xtensa/espressif: fix lock-order deadlock in esp_wifi_event_handler() - #20178
Merged
xiaoxiang781216 merged 1 commit intoSep 18, 2026
Conversation
esp_wifi_event_handler() held esp_wifi_lock() across the whole event switch, including the esp_wlan_*_hook() calls (WIFI_EVENT_STA_CONNECTED/_DISCONNECTED, WIFI_EVENT_AP_START/_STOP). Those hooks reach netdev_lower_carrier_on()/_off(), which take the per-device netdev_lock(). Every other path into esp_wifi_lock() acquires the two locks in the opposite order -- the netdev ifdown path holds netdev_lock() around its own call into esp_wifi_api_stop(), which calls esp_wifi_lock(). An application that disconnects Wi-Fi (wpa_driver_wext_disconnect() immediately followed by wapi_set_ifdown()) races the resulting WIFI_EVENT_STA_DISCONNECTED callback against its own ifdown call, and the two lock orders wedge each other permanently. Confirmed on real ESP32-S3 hardware (XIAO ESP32-S3, CONFIG_ESPRESSIF_WIFI + CONFIG_PM + CONFIG_SCHED_TICKLESS): the disconnecting task and the low-priority work-queue thread each waited on a mutex held by the other (checked live via JTAG/GDB, not inferred from code reading alone). Reproduced 4/4 times before this fix, 0/2 after. Fix: esp_wifi_lock() is now taken only around the specific calls that reach into the Wi-Fi driver API (esp_wifi_scan_event_parse(), esp_wifi_set_ps()), never spanning a esp_wlan_*_hook() call -- netdev_lock() first (or absent), esp_wifi_lock() last, on every path. Signed-off-by: Felipe Moura <moura.fmo@gmail.com> Assisted-by: Claude:claude-sonnet-5
FelipeMdeO
force-pushed
the
fix/esp-wifi-event-handler-netdev-lock-deadlock
branch
from
September 17, 2026 17:48
e08ca89 to
faae980
Compare
xiaoxiang781216
approved these changes
Sep 18, 2026
jerpelea
approved these changes
Sep 18, 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
esp_wifi_event_handler()heldesp_wifi_lock()across the entireevent switch, including the
esp_wlan_*_hook()calls(
WIFI_EVENT_STA_CONNECTED/_DISCONNECTED,WIFI_EVENT_AP_START/_STOP).Those hooks reach
netdev_lower_carrier_on()/_off(), which take theper-device
netdev_lock().Every other path into
esp_wifi_lock()acquires the two locks in theopposite order: e.g. the netdev
ifdownpath holdsnetdev_lock()around its own call into
esp_wifi_api_stop(), which callsesp_wifi_lock(). So:netdev_lock()→esp_wifi_lock()esp_wifi_lock()→netdev_lock()An application that disconnects Wi-Fi (
wpa_driver_wext_disconnect()immediately followed by
wapi_set_ifdown(), e.g.netlib_ifdown()/wapi_set_ifdown()right after a disconnect ioctl)races the resulting
WIFI_EVENT_STA_DISCONNECTEDcallback against itsown ifdown call. The two lock orders wedge each other permanently — an
AB-BA deadlock, confirmed on real ESP32-S3 hardware (XIAO ESP32-S3,
CONFIG_ESPRESSIF_WIFI+CONFIG_PM+CONFIG_SCHED_TICKLESS).Fix:
esp_wifi_lock()is now taken only around the specific calls thatreach into the Wi-Fi driver API (
esp_wifi_scan_event_parse(),esp_wifi_set_ps()), never spanning aesp_wlan_*_hook()call. Thiskeeps
netdev_lock()first (or absent) andesp_wifi_lock()last onevery path, matching the order every other caller already uses.
Impact
worked correctly before; only removes a deadlock window that was
always latent whenever
WIFI_EVENT_STA_DISCONNECTED/_CONNECTED(orthe AP equivalents) could race an ifdown/ifup on the same device.
arch/xtensa/src/common/espressif/code shared by every XtensaEspressif target that enables
CONFIG_ESPRESSIF_WIFI.Testing
I confirm that changes are verified on local setup and works as intended:
xtensa-esp-elf-gcc(crosstool-NGesp-14.2.0_20241119, 14.2.0).
CONFIG_ESPRESSIF_WIFI=y,CONFIG_PM=y,CONFIG_SCHED_TICKLESS=y,CONFIG_NETINIT_DHCPC=y, real Wi-Fi AP (WPA2/CCMP) + DHCP.wpa_driver_wext_disconnect()immediately followed by
wapi_set_ifdown()on the STA interface,with no delay between the two calls.
Confirmed via JTAG (OpenOCD + GDB) that both wedged tasks were genuinely
blocked (not merely slow): the disconnecting task waited on a mutex
whose
sem.val.mholderfield named the kernel low-priority work-queuethread as holder, and that work-queue thread waited on a mutex naming
the disconnecting task right back — the two lock orders above,
confirmed live in memory, not inferred from code reading alone.
Console log before the fix (
ps, ~20s after the disconnecting call,no further progress ever observed):
(
Mutex:Nnames the holder's TID — seesched/sched/sched_get_stateinfo.c. TID 9 waits on a mutex held by TID2 (
lpwork); TID 2 waits on a mutex held by TID 9 — a two-task cycle.)Console log after the fix, same reproduction, no hang, all application
worker threads alive and running:
Reproduced 4/4 times before the fix, 0/2 after (both clean runs reached
full application startup with the disconnect/reconnect path exercised).