Skip to content

fix(usb_serial_jtag): flush TX FIFO at end of usb_serial_jtag_write (IDFGH-17790)#18706

Open
MaikVermeulen wants to merge 1 commit into
espressif:masterfrom
MaikVermeulen:fix/usj-vfs-flush-txfifo-after-write
Open

fix(usb_serial_jtag): flush TX FIFO at end of usb_serial_jtag_write (IDFGH-17790)#18706
MaikVermeulen wants to merge 1 commit into
espressif:masterfrom
MaikVermeulen:fix/usj-vfs-flush-txfifo-after-write

Conversation

@MaikVermeulen

Copy link
Copy Markdown

Summary

usb_serial_jtag_write() (the USB-Serial/JTAG VFS write path) emits every byte but never flushes the TX FIFO. The final bytes of a write — and the zero-length packet that terminates a 64-byte-multiple USB transfer — can therefore linger in the FIFO and never be delivered across a USB re-enumeration after a chip reset. On ESP32-C6/H2 this intermittently wedges the host CDC pipe:

  • Console (no-driver path): missing printf/log output on cold boot, especially with logging disabled.
  • OpenThread RCP (driver-backed path): OpenThread sends every spinel frame via otPlatUartSend → write() → usb_serial_jtag_write, and otPlatUartFlush() is a no-op, so the spinel link goes dead and the host's TLS/handshake never gets a response. Recovery requires a physical unplug.

The fix mirrors the existing '\n' handling in usb_serial_jtag_tx_char_no_driver() and the fsync path, which already flush to "make sure the line doesn't linger in fifo" — we simply flush once at the end of every write.

        s_ctx.tx_func(fd, c);
    }
    usb_serial_jtag_ll_txfifo_flush();   // <-- added
    _lock_release_recursive(&s_ctx.write_lock);
    return size;

Validation

Reproduced and fixed on physical ESP32-C6 hardware via an A/B cold-plug test (physical VBUS power-cycle per iteration, one handshake-over-USB attempt per boot, logging disabled — the condition that exposes the bug). The only difference between the two images was this flush:

Build Flush Result
Baseline absent ~33% timeouts (host never gets a response)
Fixed present 0 timeouts over 20 consecutive cold-plugs

References

The fix was first posted by me in the discussion on #17432.

The VFS write path emits bytes but never flushes the TX FIFO, so the
final bytes of a write (and the zero-length packet that terminates a
64-byte-multiple USB transfer) can linger in the FIFO. On ESP32-C6/H2
this intermittently leaves data undelivered across a USB
re-enumeration after a chip reset, wedging the host CDC pipe: missing
console output, and a dead OpenThread RCP spinel link (every spinel
frame is sent via write() -> usb_serial_jtag_write, and
otPlatUartFlush() is a no-op).

Flushing the TX FIFO at the end of usb_serial_jtag_write() makes the
final bytes reach the host. Validated on ESP32-C6 hardware with an
A/B cold-plug test: baseline ~33% handshake-timeout rate over the USB
serial link dropped to 0/20 with the flush in place.

Closes espressif#17432
Ref: https://github.com/orgs/openthread/discussions/11627
@CLAassistant

CLAassistant commented Jun 10, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions Bot changed the title fix(usb_serial_jtag): flush TX FIFO at end of usb_serial_jtag_write fix(usb_serial_jtag): flush TX FIFO at end of usb_serial_jtag_write (IDFGH-17790) Jun 10, 2026
@espressif-bot espressif-bot added the Status: Opened Issue is new label Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status: Opened Issue is new

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ESP32-C6/H2 USB_SERIAL_JTAG not initialised correctly when logging is disabled (IDFGH-16276)

4 participants