diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index c22d49e6654..e6790dc9d54 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -739,6 +739,10 @@ async def to_code(config): if CORE.is_esp8266: cg.add_library("ESP8266WiFi", None) + # Route the SDK's management frame allocator through the wrapper in + # wifi_component_esp8266.cpp so an empty small frame pool does not hang + # the device in pm_send_nullfunc. + cg.add_build_flag("-Wl,--wrap=ieee80211_getmgtframe") if CONF_PHY_MODE in config: cg.add_define("USE_WIFI_PHY_MODE") cg.add(var.set_phy_mode(config[CONF_PHY_MODE])) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 60ec3f9a4d5..98742d16c2c 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -38,6 +38,28 @@ extern "C" { #include "esphome/core/progmem.h" #include "esphome/core/util.h" +// Management frames of 64 bytes or less come from a fixed pool of eight SDK buffers. +// Larger frames are allocated from the heap and freed on completion. +static constexpr int SMALL_MGMT_FRAME_MAX = 64; +// Times the small pool was empty and a heap buffer was handed out instead. +static uint8_t small_mgmt_pool_exhausted_count = 0; + +extern "C" { +void *__real_ieee80211_getmgtframe(void **frm, int headroom, int pktlen); + +// When the small pool is empty the SDK returns nullptr and pm_send_nullfunc spins in an +// assert until the soft watchdog resets the device (#19371). Retry with a size just above +// the pool limit so the SDK takes the heap backed path it already uses for larger frames. +void *__wrap_ieee80211_getmgtframe(void **frm, int headroom, int pktlen) { + void *buf = __real_ieee80211_getmgtframe(frm, headroom, pktlen); + if (buf != nullptr || headroom + pktlen > SMALL_MGMT_FRAME_MAX) + return buf; + if (small_mgmt_pool_exhausted_count != UINT8_MAX) + small_mgmt_pool_exhausted_count++; + return __real_ieee80211_getmgtframe(frm, headroom, SMALL_MGMT_FRAME_MAX + 1 - headroom); +} +} + namespace esphome::wifi { static const char *const TAG = "wifi_esp8266"; @@ -981,6 +1003,11 @@ void WiFiComponent::process_pending_callbacks_() { // to main loop context (full stack). Connect state listeners are handled // by notify_connect_state_listeners_() in the shared state machine code. + if (small_mgmt_pool_exhausted_count != 0) { + ESP_LOGW(TAG, "SDK small management frame pool empty %u times, used heap instead", small_mgmt_pool_exhausted_count); + small_mgmt_pool_exhausted_count = 0; + } + #ifdef USE_WIFI_CONNECT_STATE_LISTENERS if (this->pending_.disconnect) { this->pending_.disconnect = false;