arch/xtensa/espressif: initialize the HR Timer from the Wi-Fi init path - #19786
Merged
Merged
Conversation
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 <ricard@groundbits.com>
Assisted-by: Claude Opus 5 (Claude Code)
xiaoxiang781216
approved these changes
Aug 11, 2026
xiaoxiang781216
approved these changes
Aug 11, 2026
Contributor
|
Nice catch! |
fdcavalcanti
approved these changes
Aug 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Espressif Wi-Fi stack cannot work unless the
esp_timersubsystem has beeninitialized, but nothing in the Wi-Fi code does that — the initialization is
left to each board's bringup, which has to call
esp_hr_timer_init()beforeboard_wlan_init(). Every in-tree ESP32/ESP32-S2/ESP32-S3 board with a Wi-Fidefconfig happens to make that call, so in-tree configurations work today; any
board that does not (a new port, or a board file written by following another
subsystem's example) dies on the first RF enable, with a failure mode that gives
no diagnostic whatsoever.
This moves the initialization into the common Wi-Fi init path, where the
requirement actually originates, so the Wi-Fi stack no longer depends on board
code getting an undocumented ordering right.
The dependency
The requirement is invisible from the Wi-Fi sources. Bring-up runs:
and then, the first time the radio is powered up:
esp_timer_create()/esp_timer_start_periodic()returnESP_ERR_INVALID_STATEwhileesp_timeris uninitialized. The HAL wouldnormally initialize itself from its
esp_timer_init_os()startup hook, but thathook is compiled out on NuttX (
#ifndef __NuttX__incomponents/esp_timer/src/esp_timer.c), so the timer task and the timer ISR areonly ever created from NuttX's
esp_hr_timer_init()->esp_timer_init().phy_track_pll_init()is reached on every target except the original ESP32(
#if !CONFIG_IDF_TARGET_ESP32 && !CONFIG_ESP_PHY_DISABLE_PLL_TRACK), and PLLtracking is enabled in the NuttX
sdkconfig.hfor esp32s2/s3/c3/c6/h2.The change
esp_wifi_api_adapter_init()(arch/xtensa/src/common/espressif/) nowcalls
esp_hr_timer_init()beforeesp_wifi_init(), with a comment recording thephy_track_pll_init()->esp_timerchain, since that constraint is not obvious from the call site.esp_hr_timer_init()is idempotent (it early-returns once the subsystem isup), so the boards that already initialize it during bringup are unaffected —
the second call returns
OKimmediately.ESPRESSIF_WIRELESSnowselectsESPRESSIF_HR_TIMERexplicitly.Today it inherits it only indirectly, through the deprecated
ESP32_RT_TIMER/ESP32S2_RT_TIMER/ESP32S3_RT_TIMERsymbols whose soleremaining job is to select
ESPRESSIF_HR_TIMER. Since the timer adapter(
esp_timer_adapter.c) is only compiled whenESPRESSIF_HR_TIMER=y, thedependency should be stated where the radio is enabled rather than routed
through compatibility symbols. The RISC-V
ESPRESSIF_WIRELESSalready hasthis
select.No behavioral change for any in-tree defconfig:
ESPRESSIF_HR_TIMERis alreadyyin every wireless configuration, and every in-tree Wi-Fi board already callsesp_hr_timer_init()from bringup, so on those the added call is a no-opearly return.
How it was diagnosed
Found while bringing up an out-of-tree ESP32-S3 board whose bringup did not have
the call. The failure has 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 — which is why it went unexplained for a month.
It was tracked down with ROM-level
ets_printf()breadcrumbs along the wholeinit path, plus a high-priority thread that busy-waits on
ets_delay_us()instead of sleeping:
phy_track_pll_init()and never reaches theprint immediately after it;
sleep()-based thread stopswaking — i.e. the tick is gone, the CPU is not.
Initializing the timer ahead of Wi-Fi init turns the same image into a working
one: the board associates to an AP, obtains a DHCP lease and serves telnet.
Testing
associates, DHCP lease obtained, telnet session served. Without the
initialization the same image locks up as described above.
esp32s3-devkit:wifi(xtensa-esp32s3-elf) builds clean, no newwarnings, links and images successfully.
tools/checkpatch.sh -g HEAD: all checks pass.same unenforced dependency, but nothing is broken there today: its
ESPRESSIF_WIRELESSalready selects bothESPRESSIF_HR_TIMERandRTC_DRIVER, andesp_rtc.cinitializes the timer. An earlier draft of thispatch mirrored the change there too; it was dropped rather than posted
untested, since there is no RISC-V toolchain on this machine. Happy to add it
if maintainers would prefer the common-source trees kept symmetric.
Disclosure
This root-cause analysis, patch, and hardware validation were performed by an AI
agent (Claude Code, operated and directed by the submitter), and the result was
reviewed by the submitter before posting.