Skip to content

USB-NCM suspend/resume on iOS #3445

@mickeyl

Description

@mickeyl

Operating System

Others

Commit SHA

Version bundled with ESP-IDF 5.5.2

Board

ESP32S3

Firmware

Can be reproduced with examples/peripherals/usb/device/tusb_ncm

What happened ?

When using USB-NCM with iOS devices (iPhone/iPad), the network connection often fails to re-establish after the host wakes up from sleep.

The iOS host wakes up and immediately sends a DHCP DISCOVER or REQUEST.
The ESP32 application receives this and attempts to send a DHCP OFFER/ACK.
The transmission fails with ESP_ERR_INVALID_STATE because tud_ready() returns false.

tud_ready() seems to imply the device is both Mounted AND Not Suspended. During the resume transition, there is a race condition where the host has started network traffic, but the TinyUSB stack state may still be effectively "Suspended" or transitioning. By rejecting the transmission at the driver level, the critical DHCP response is dropped, causing the iOS device to eventually time out and assign a Link-Local address.

I could make it work by relaxing the check in tinyusb_net_send_sync and tinyusb_net_send_async from tud_ready() to tud_mounted().

If the device is mounted but currently suspended (or resuming), it is safe to queue the packet to the endpoint. The hardware USB controller will hold the packet until the bus signaling is active, rather than the software driver preemptively dropping it.

Validation Tested with iPhone/iPad. This change, combined with application-level handling (gratuitous ARP and broadcast flags), allows the connection to recover (almost) instantly after the device wakes up.

Here's patch

 esp_err_t tinyusb_net_send_async(void *buffer, uint16_t len, void *buff_free_arg)
 {
-    if (!tud_ready()) {
+    if (!tud_mounted()) {
         return ESP_ERR_INVALID_STATE;
     }
 esp_err_t tinyusb_net_send_sync(void *buffer, uint16_t len, void *buff_free_arg, TickType_t  timeout)
 {
-    if (!tud_ready()) {
+    if (!tud_mounted()) {
         return ESP_ERR_INVALID_STATE;
     }

How to reproduce ?

  1. Run the example on the MCU
  2. Open Settings on the iOS device
  3. Plugin and immediately see Ethernet -> Espressif Device appearing and receiving an IP
  4. Suspend and leave it iOS device for a moment
  5. See the Espressif Device having lost the IP
  6. See DHCP REQUEST/ACK traffic which does nothing on the iPhone side.
  7. Observe DHCP DISCOVER/OFFER loop, which does nothing on the iPhone side
  8. Observe iPhone getting a private IP.

Debug Log as txt file (LOG/CFG_TUSB_DEBUG=2)

See above

Screenshots

No response

I have checked existing issues, discussion and documentation

  • I confirm I have checked existing issues, discussion and documentation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions