detach USB device stack before reboot (esp_restart_noos leaves the PHY attached)

This commit is contained in:
liquidraver
2026-08-28 10:46:46 +02:00
parent de95f6c6c5
commit bc208ade26
3 changed files with 64 additions and 0 deletions
+33
View File
@@ -61,6 +61,26 @@
#define ESP32_FORCE_DOWNLOAD_BOOT 1
#endif
/* Detach the USB device stack before a reset so the host sees a real unplug —
* see zephcore_usbd_detach(). Matters most on the ESP32-S3 USB companion,
* whose OTG PHY survives a soft reset with D+ still pulled up.
*
* The condition below must be EXACTLY the one CMakeLists.txt uses to compile
* adapters/usb/ZephyrUSBCDC.cpp, or this call site compiles against an
* implementation that is never linked (findings #22 — that failure mode has
* already cost this repo four separate undefined-reference bugs). The
* repeater/observer/room-server branches use the inner half alone; the
* companion branch wraps it in (LOG || COMPANION_USB || COMPANION_SERIAL), and
* ZEPHCORE_COMPANION is the compile definition that identifies that branch. */
#if !defined(CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT) && \
(defined(CONFIG_USB_CDC_ACM) || defined(CONFIG_USBD_CDC_ACM_CLASS))
#if !defined(ZEPHCORE_COMPANION) || defined(CONFIG_LOG) || \
defined(CONFIG_ZEPHCORE_COMPANION_USB) || defined(CONFIG_ZEPHCORE_COMPANION_SERIAL)
#include <ZephyrUSBCDC.h>
#define ZEPHCORE_USBD_DETACH 1
#endif
#endif
/* LoRa TX activity LED (optional — defined per-board via DT alias) */
#if DT_NODE_EXISTS(DT_ALIAS(lora_tx_led))
static const struct gpio_dt_spec tx_led =
@@ -295,6 +315,11 @@ void ZephyrBoard::onAfterTransmit()
void ZephyrBoard::reboot()
{
k_msleep(50); /* Let UART/USB flush */
#ifdef ZEPHCORE_USBD_DETACH
/* USB device stack (CDC ACM): detach so the host sees an unplug. */
zephcore_usbd_detach();
k_msleep(100); /* Hold SE0 long enough for host disconnect debounce */
#endif
#ifdef ESP32_USB_SERIAL_DETACH
/* Drop the D+ pull-up so the host sees a clean USB disconnect before the
* soft reset (GH #43 — wedged serial port on macOS). */
@@ -317,6 +342,14 @@ void ZephyrBoard::rebootToBootloader()
REG_SET_BIT(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT);
#endif
k_msleep(50); /* Let UART/USB flush */
#ifdef ZEPHCORE_USBD_DETACH
/* Detach the CDC ACM device — same reason as reboot(), and doubly so here:
* the ESP32-S3 comes back in ROM download mode, where the port belongs to
* USB-Serial-JTAG. The host must see the old device leave the bus first or
* it will not enumerate the new one. */
zephcore_usbd_detach();
k_msleep(100);
#endif
#ifdef ESP32_USB_SERIAL_DETACH
/* Clean USB disconnect before the soft reset — same reason as reboot(). */
usb_serial_jtag_ll_phy_enable_pad(false);
+24
View File
@@ -267,3 +267,27 @@ extern "C" void zephcore_usbd_set_dtr_cb(zephcore_usbd_cdc_dtr_cb_t cb)
{
s_dtr_cb = cb;
}
extern "C" void zephcore_usbd_detach(void)
{
if (!s_initialized) {
return;
}
/* usbd_disable() -> udc_disable() drops the D+ pull-up, which is the only
* thing that makes the host see an unplug. A soft reset does not: on the
* ESP32-S3, esp_restart_noos() resets WiFi/BT, timers, SPI, UART, DMA and
* crypto but nothing USB, so the PHY pad stays enabled across the reset
* and the host keeps talking to an endpoint the firmware has abandoned.
* (Same root cause as GH #43, which hit the USB-Serial-JTAG controller;
* this covers the USB OTG one that the CDC companion transport uses.)
*
* Safe from the usbd message callback: CONFIG_USBD_MSG_DEFERRED_MODE is
* on by default, so that callback runs on the system workqueue rather
* than in the device stack context, and usbd_disable() is not re-entered
* from the thread it stops.
*/
(void)usbd_disable(&zephcore_usbd);
s_initialized = false;
s_dtr_active = false;
}
+7
View File
@@ -40,6 +40,13 @@ bool zephcore_usbd_is_dtr_active(void);
* Companion uses this to reset its RX state and flip active_iface on drop. */
void zephcore_usbd_set_dtr_cb(zephcore_usbd_cdc_dtr_cb_t cb);
/* Detach from the USB bus (drop the D+ pull-up) so the host sees an unplug.
* Call before a reset: a soft reset leaves the PHY attached on some SoCs, and
* the host then keeps a stale endpoint open across the reboot. No-op if the
* stack was never initialized; after this, zephcore_usbd_init() would have to
* run again to come back. */
void zephcore_usbd_detach(void);
#ifdef __cplusplus
}
#endif