Skip to content

fix(usbh_cdc/ecm): fix bulk pipe stalls for CDC-ECM on SW reboot and alt-setting switch#711

Open
siaffaf wants to merge 1 commit into
espressif:masterfrom
siaffaf:fix/usbh-cdc-ecm-bulk-out-halt
Open

fix(usbh_cdc/ecm): fix bulk pipe stalls for CDC-ECM on SW reboot and alt-setting switch#711
siaffaf wants to merge 1 commit into
espressif:masterfrom
siaffaf:fix/usbh-cdc-ecm-bulk-out-halt

Conversation

@siaffaf
Copy link
Copy Markdown

@siaffaf siaffaf commented May 3, 2026

Problem

When using iot_usbh_ecm on ESP32-S3, two failure modes were observed:

  1. Software reboot (no power cycle): After esp_restart(), the USB
    device is not power-cycled and retains its state. A failed OUT transfer
    from the previous session leaves the bulk OUT pipe in HALTED state,
    blocking all transmits permanently until a hardware power cycle.

  2. IN transfer chain stops after SET_INTERFACE: The bulk IN endpoint
    does not physically exist while the data interface is in alt=0. The
    initial IN transfer fails with STATUS_ERROR, leaving the pipe HALTED.
    After SET_INTERFACE(alt=1) the endpoint becomes available but the
    receive chain never restarts.

  3. Missing ZLP on bulk OUT: Ethernet frames whose size is an exact
    multiple of the 64-byte bulk MPS were not followed by a Zero Length
    Packet, causing the device to wait indefinitely for more data
    (CDC-ECM spec §3.3.1).

Fix

iot_usbh_cdc.c / iot_usbh_cdc.h

  • in_xfer_cb CANCELED case: resubmit the IN transfer instead of
    dropping it. USB_TRANSFER_STATUS_CANCELED fires when the USB host
    flushes the endpoint during SET_INTERFACE — resubmitting here restarts
    the chain automatically once alt=1 is active.

  • in_xfer_cb default (error) case: remove the
    _cdc_reset_transfer_endpoint + resubmit retry. Leave the pipe HALTED
    so the USB host client's internal USBH_EP_CMD_FLUSH can complete
    without hitting ESP_ERR_INVALID_STATE. The ECM layer restarts the
    transfer explicitly after SET_INTERFACE.

  • usbh_cdc_restart_in_transfer() (new): clears the HALTED IN
    pipe and resubmits the IN transfer. Called by the ECM layer after
    SET_INTERFACE(alt=1) completes.

  • usbh_cdc_restart_out_transfer() (new): clears the HALTED OUT
    pipe and releases the semaphore only when the pipe is actually stalled
    (semaphore count == 0). Safe to call unconditionally on both HW and
    SW reboots — no-op when the pipe is healthy.

  • USB_TRANSFER_FLAG_ZERO_PACK set on the bulk OUT transfer at
    allocation time.

iot_usbh_ecm.c

Call usbh_cdc_restart_in_transfer() and usbh_cdc_restart_out_transfer()
after _ecm_config_intf() (i.e. after SET_INTERFACE(alt=1) completes).

Testing

Tested on ESP32-S3 with a USB modem (VID:1F94, PID:3002).

  • Hardware reboot: ECM connects normally, no regression ✓
  • Software reboot (esp_restart()): ECM now connects reliably ✓
  • Frames with size that is exact multiple of 64 bytes: transmitted correctly ✓

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 3, 2026

CLA assistant check
All committers have signed the CLA.

…alt-setting switch

Three related fixes for CDC-ECM bulk data pipe reliability:

1. in_xfer_cb: handle CANCELED separately — resubmit the IN transfer so
   the receive chain restarts automatically after SET_INTERFACE flushes
   the endpoint during alt=0→alt=1 switch. Remove the retry logic from
   the default (error) case: leave the pipe HALTED so the USB host
   client's USBH_EP_CMD_FLUSH can run without hitting
   ESP_ERR_INVALID_STATE.

2. Add usbh_cdc_restart_in_transfer(): called by the ECM layer after
   SET_INTERFACE(alt=1) completes to clear the HALTED IN pipe and
   resubmit the IN transfer chain.

3. Add usbh_cdc_restart_out_transfer(): on a software reboot the device
   is not power-cycled, so the OUT pipe may be left HALTED from the
   previous session. Clears the halt and releases the semaphore, guarded
   by a semaphore-count check so it is a no-op on a healthy pipe (safe
   to call on both HW and SW reboots).

4. Set USB_TRANSFER_FLAG_ZERO_PACK on the bulk OUT transfer so a ZLP is
   sent when payload size is an exact multiple of the 64-byte MPS, as
   required by CDC-ECM spec §3.3.1.

Both restart functions are called from iot_usbh_ecm after
SET_INTERFACE(alt=1) completes.

Tested on ESP32-S3 with a USB modem (VID:1F94 PID:3002),verified on both
hardware power-cycle and software reboot (esp_restart()).
@siaffaf siaffaf force-pushed the fix/usbh-cdc-ecm-bulk-out-halt branch from d22111c to 24a9f26 Compare May 3, 2026 20:42
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.

2 participants