From eef6a488a2ada19f46e8c36efd1f82a419a5dfa0 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Fri, 4 Sep 2026 12:14:05 +0200 Subject: [PATCH] esp flash fix --- docs/Repeater_CLI_commands.md | 2 +- zephcore/adapters/board/ZephyrBoard.cpp | 39 +++++++++++++++++++++---- zephcore/boards/common/esp32s3_usb.conf | 5 ++-- zephcore/helpers/CommonCLI.cpp | 21 ++++++++++--- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/docs/Repeater_CLI_commands.md b/docs/Repeater_CLI_commands.md index 51f1186..d7b869b 100644 --- a/docs/Repeater_CLI_commands.md +++ b/docs/Repeater_CLI_commands.md @@ -27,7 +27,7 @@ All commands are sent over USB serial (CDC-ACM). Commands sent remotely over the | `ver` | Firmware version and build date | | `board` | Board manufacturer name | | `reboot` | Reboot immediately | -| `start dfu` | nRF52: reboot into the UF2 bootloader for drag-and-drop update. ESP32-S3: reboot into the ROM download mode so esptool can reach the chip | +| `start dfu` | nRF52: reboot into the UF2 bootloader for drag-and-drop update. ESP32-S3: reboot into the ROM download mode on USB-Serial-JTAG (`303a:1001`), so ordinary `esptool write-flash` and browser flashers can reach the chip. Every other chip replies with an error and does **not** reboot — ESP32-C3/C6 and classic ESP32 never lose the port to USB OTG so esptool resets them itself, and nRF54L15/MG24/STM32WL have no USB device peripheral and are flashed over SWD | | `start ota` | ESP32: start WiFi AP + HTTP OTA server. nRF52: reboot into BLE OTA DFU mode | | `stop ota` | Stop WiFi OTA server (ESP32 only) | | `clkreboot` | Set clock to a fixed reference time (15 May 2024 8:50pm UTC) then reboot | diff --git a/zephcore/adapters/board/ZephyrBoard.cpp b/zephcore/adapters/board/ZephyrBoard.cpp index 95aa504..4832079 100644 --- a/zephcore/adapters/board/ZephyrBoard.cpp +++ b/zephcore/adapters/board/ZephyrBoard.cpp @@ -45,9 +45,24 @@ * * FORCE_DOWNLOAD_BOOT lives in the RTC domain, which a software system reset * does not clear (only a power-on reset does), so setting it and rebooting - * brings the ROM up in download mode with USB-Serial-JTAG back on the pads. - * That gives both `start dfu` and the Arduino-style 1200-baud touch a way to - * hand the port back to esptool / esptool-js / the web configurator. + * brings the ROM up in download mode. That gives both `start dfu` and the + * Arduino-style 1200-baud touch a way to hand the port back to esptool / + * esptool-js / the web configurator. + * + * Setting it is not enough on its own, though. WHICH USB controller the ROM + * appears on is a separate mux, and it lives in the same RTC domain: Zephyr's + * DWC2 quirk layer claims the internal PHY for USB OTG at init + * (usb_wrap_ll_phy_enable_external(hw, false) -> RTC_CNTL_USB_CONF_REG + * SW_HW_USB_PHY_SEL=1, SW_USB_PHY_SEL=1), and those bits survive the reboot + * exactly like FORCE_DOWNLOAD_BOOT does. Left alone the ROM therefore comes up + * on USB OTG (303a:0009), not USB-Serial-JTAG (303a:1001). That state is still + * flashable -- esptool reports "USB mode: USB-OTG" and even loads the stub -- + * but it breaks everything that expects USJ: esptool's auto-reset does nothing + * over ROM OTG CDC so it needs --before no-reset, and esptool-js special-cases + * only PID 0x1001, so browser flashers fall into a classic DTR/RTS reset path + * that cannot work. Clearing SW_HW_USB_PHY_SEL hands the mux back to + * hardware/eFuse control, whose default routes the internal PHY to USJ -- i.e. + * it reproduces what a power-on reset would have left behind. * * Same register and sequence Arduino-ESP32 uses in usb_persist_restart() * (RESTART_BOOTLOADER). OPTION1 carries no other bits on this SoC, but set the @@ -426,8 +441,9 @@ void ZephyrBoard::rebootToBootloader() nrf_power_gpregret_set(NRF_POWER, 0, BOOTLOADER_DFU_UF2_MAGIC); #endif #ifdef ESP32_FORCE_DOWNLOAD_BOOT - /* Come back up in the ROM download mode instead of the app, handing the - * USB port back to USB-Serial-JTAG so esptool can reach it. */ + /* Come back up in the ROM download mode instead of the app. Which USB + * controller it lands on is settled by the PHY mux below, after the + * detach. */ REG_SET_BIT(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT); #endif k_msleep(50); /* Let UART/USB flush */ @@ -443,6 +459,19 @@ void ZephyrBoard::rebootToBootloader() /* Clean USB disconnect before the soft reset — same reason as reboot(). */ usb_serial_jtag_ll_phy_enable_pad(false); k_msleep(100); +#endif +#ifdef ESP32_FORCE_DOWNLOAD_BOOT + /* Hand the internal USB PHY back to USB-Serial-JTAG, so the ROM download + * mode we just armed appears as 303a:1001 rather than ROM USB OTG. See + * the header comment on ESP32_FORCE_DOWNLOAD_BOOT for why this is needed + * and why it has to happen HERE -- after zephcore_usbd_detach(), which + * has to drop the D+ pull-up while OTG still owns the PHY, or the host + * never sees a clean disconnect. + * + * No-op on builds that never took the PHY (repeater/observer/debug keep + * USJ, so both bits are already 0). */ + REG_CLR_BIT(RTC_CNTL_USB_CONF_REG, RTC_CNTL_SW_HW_USB_PHY_SEL); + REG_CLR_BIT(RTC_CNTL_USB_CONF_REG, RTC_CNTL_SW_USB_PHY_SEL); #endif sys_reboot(SYS_REBOOT_COLD); } diff --git a/zephcore/boards/common/esp32s3_usb.conf b/zephcore/boards/common/esp32s3_usb.conf index 0624f84..f334d4a 100644 --- a/zephcore/boards/common/esp32s3_usb.conf +++ b/zephcore/boards/common/esp32s3_usb.conf @@ -14,8 +14,9 @@ # # NOTE: this takes the USB port away from USB-Serial-JTAG, so esptool's # auto-reset into download mode stops working on boards flashed over native USB. -# `start dfu` and the 1200-baud touch set FORCE_DOWNLOAD_BOOT to give it back -# (ZephyrBoard::rebootToBootloader); otherwise it is the BOOT button. +# `start dfu` and the 1200-baud touch give it back (ZephyrBoard::rebootToBootloader +# sets FORCE_DOWNLOAD_BOOT and clears the RTC-domain PHY mux, so the ROM returns +# on USJ as 303a:1001); otherwise it is the BOOT button. # # Also add the cdc_acm_uart DTS node to the board overlay (the overlays include # boards/common/esp32s3_usb_otg.dtsi). Without the DT node, diff --git a/zephcore/helpers/CommonCLI.cpp b/zephcore/helpers/CommonCLI.cpp index 9eddfee..b99b9e5 100644 --- a/zephcore/helpers/CommonCLI.cpp +++ b/zephcore/helpers/CommonCLI.cpp @@ -268,13 +268,26 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch /* Reboot into the chip's own firmware-update mode. nRF52: the Adafruit * UF2 bootloader. ESP32-S3: the ROM download mode, which is the only * way to get the USB port back from the CDC companion transport for - * esptool (see ZephyrBoard::rebootToBootloader). */ + * esptool (see ZephyrBoard::rebootToBootloader). + * + * Everything else has no bootloader this command can reach, so it must + * NOT reboot. rebootToBootloader() would just reset back into the app, + * dropping the node off the mesh for nothing while replying that it had + * gone somewhere it cannot go. C3/C6 and classic ESP32 do not need it + * anyway -- they have no DWC2 controller, so USB-Serial-JTAG never + * loses the port and esptool resets them itself; nRF54L15/MG24/STM32WL + * have no USB device peripheral at all and are flashed over SWD. */ #if defined(CONFIG_SOC_SERIES_ESP32S3) strcpy(reply, "OK - rebooting to ESP32 download mode"); -#else - strcpy(reply, "OK - rebooting to UF2 DFU"); -#endif scheduleReboot(REBOOT_DFU); +#elif defined(CONFIG_SOC_SERIES_NRF52) + strcpy(reply, "OK - rebooting to UF2 DFU"); + scheduleReboot(REBOOT_DFU); +#elif defined(CONFIG_SOC_FAMILY_ESPRESSIF_ESP32) + strcpy(reply, "Err: no DFU mode here - esptool resets this chip itself"); +#else + strcpy(reply, "Err: no DFU bootloader on this chip - flash over SWD"); +#endif } else if (memcmp(command, "start ota", 9) == 0) { #if IS_ENABLED(CONFIG_ZEPHCORE_WIFI_OTA) /* ESP32: Start WiFi AP + HTTP OTA server (no reboot) */