[wifi] Keep the ESP8266 SDK from hanging when its small management frame pool is empty

With power_save_mode NONE the SDK sends a null function frame every 10 s from its
power management timer. The 24 byte frame comes from a fixed pool of eight small
buffers; when that pool is empty ieee80211_getmgtframe returns NULL and
pm_send_nullfunc asserts into an endless loop until the soft watchdog resets the
device. Wrap the allocator so an empty pool falls back to the heap backed path the
SDK already uses for larger frames, and log when that happens.
This commit is contained in:
J. Nick Koston
2026-09-17 16:37:48 -05:00
parent f6897f2257
commit 6011a10993
2 changed files with 31 additions and 0 deletions
+4
View File
@@ -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]))
@@ -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;