From b1d76fdbd7c8468b94917ea9ec8eb78049a7e6f0 Mon Sep 17 00:00:00 2001 From: Ricard Rosson Date: Tue, 11 Aug 2026 09:17:59 +0100 Subject: [PATCH] arch/xtensa/espressif: initialize the HR Timer from the Wi-Fi init path The Espressif Wi-Fi stack cannot work unless the esp_timer subsystem has been initialized, but nothing in the Wi-Fi code does that: it is left to each board's bringup to call esp_hr_timer_init() first. Any board that does not happen to make that call dies on the first RF enable. The dependency is not visible from the Wi-Fi sources. The path is: board_wlan_init() -> esp_wlan_sta_initialize() -> esp_wlan_initialize() -> esp_wifi_initialize() -> esp_wifi_api_adapter_init() and later, when the radio is first powered up: esp_phy_enable_wrapper() -> esp_phy_enable() (esp-hal-3rdparty, components/esp_phy/src/phy_init.c) -> phy_track_pll_init() (components/esp_phy/src/phy_common.c) phy_track_pll_init() calls esp_timer_create() and esp_timer_start_periodic() wrapped in ESP_ERROR_CHECK(). Both return ESP_ERR_INVALID_STATE while esp_timer is uninitialized, because the HAL's own esp_timer_init_os() startup hook is compiled out on NuttX (#ifndef __NuttX__ in components/esp_timer/src/esp_timer.c), so the timer task and the timer ISR only ever get created from NuttX's esp_hr_timer_init() -> esp_timer_init(). Initialize the HR Timer at the top of esp_wifi_api_adapter_init(), where the requirement actually originates. esp_hr_timer_init() is idempotent (it early-returns once the subsystem is up), so boards that already call it during bringup are unaffected. Also make ESPRESSIF_WIRELESS select ESPRESSIF_HR_TIMER explicitly instead of inheriting it through the deprecated ESP32{,S2,S3}_RT_TIMER symbols, so the timer adapter is guaranteed to be built whenever the radio is. This is deliberately limited to Xtensa. The RISC-V common-espressif tree has the same unenforced dependency, but nothing is broken there today: its ESPRESSIF_WIRELESS already selects both ESPRESSIF_HR_TIMER and RTC_DRIVER, and esp_rtc.c initializes the timer. The mirror change can follow from someone able to test it on RISC-V hardware. This was diagnosed on an out-of-tree ESP32-S3 board whose bringup lacked the call. The failure gives no panic output at all and looks exactly like a CPU lockup: the system tick stops, the console dies mid-line and USB stays enumerated but unresponsive. It was tracked down with ROM-level ets_printf() breadcrumbs along the init path plus a high-priority thread that busy-waits on ets_delay_us(): the breadcrumb trail ends inside phy_track_pll_init() and never reaches the print after it, and the busy-wait thread keeps printing while every sleep()-based thread stops waking, showing the tick is gone. Initializing the timer ahead of Wi-Fi init makes the same image associate to an AP, obtain a DHCP lease and serve telnet. Validated on ESP32-S3 silicon (240 MHz, no PSRAM, 16 MiB flash). esp32s3-devkit:wifi builds clean with the change. Signed-off-by: Ricard Rosson Assisted-by: Claude Opus 5 (Claude Code) --- arch/xtensa/src/common/espressif/Kconfig | 1 + .../src/common/espressif/esp_wifi_api.c | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/arch/xtensa/src/common/espressif/Kconfig b/arch/xtensa/src/common/espressif/Kconfig index 527b288567289..599a1d3e84f13 100644 --- a/arch/xtensa/src/common/espressif/Kconfig +++ b/arch/xtensa/src/common/espressif/Kconfig @@ -2045,6 +2045,7 @@ config ESPRESSIF_WIRELESS select ESP32_RNG if ARCH_CHIP_ESP32 select ESP32_RT_TIMER if ARCH_CHIP_ESP32 select ESP32_TIMER0 if ARCH_CHIP_ESP32 + select ESPRESSIF_HR_TIMER ---help--- Enable Wireless support diff --git a/arch/xtensa/src/common/espressif/esp_wifi_api.c b/arch/xtensa/src/common/espressif/esp_wifi_api.c index 8904a588789f4..c5a67e3068c50 100644 --- a/arch/xtensa/src/common/espressif/esp_wifi_api.c +++ b/arch/xtensa/src/common/espressif/esp_wifi_api.c @@ -34,6 +34,7 @@ #include "esp_wifi.h" #include "esp_private/wifi.h" +#include "esp_hr_timer.h" #include "esp_wifi_utils.h" #include "esp_wifi_api.h" @@ -112,6 +113,24 @@ int esp_wifi_api_adapter_init(void) int ret; wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT(); + /* Make sure the HR Timer (and, with it, the underlying esp_timer + * subsystem) is running before the radio is brought up. The first RF + * enable reaches phy_track_pll_init(), which calls esp_timer_create() and + * esp_timer_start_periodic() inside an ESP_ERROR_CHECK(). Those calls + * only work once esp_timer_init() has created the timer task and + * installed the timer ISR, and that only happens from + * esp_hr_timer_init(). Doing it here keeps the Wi-Fi stack independent + * of whether a particular board's bringup code happens to have + * initialized the HR Timer. The call is idempotent. + */ + + ret = esp_hr_timer_init(); + if (ret < 0) + { + wlerr("Failed to initialize HR Timer error=%d\n", ret); + return ret; + } + esp_wifi_lock(true); esp_evt_work_init();