diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 3ad40709f09..9bf8192ee1d 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -13,7 +13,6 @@ #include #include -#include #include #include #include @@ -40,6 +39,7 @@ #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" +#include "esphome/core/lock_free_queue.h" #include "esphome/core/log.h" #include "esphome/core/util.h" @@ -48,13 +48,11 @@ namespace esphome::wifi { static const char *const TAG = "wifi_esp32"; static EventGroupHandle_t s_wifi_event_group; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static QueueHandle_t s_event_queue; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -// Fast-path flag to skip xQueueReceive kernel call when queue is empty. -// Set from ESP-IDF event loop handler (task context), cleared from main loop. -// std::atomic is avoided because GCC on Xtensa generates indirect function -// calls for atomic bool operations instead of inlining them. -static std::atomic s_event_pending{0}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static esp_netif_t *s_sta_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +// Lock-free SPSC queue replaces FreeRTOS xQueue to avoid kernel spinlock +// overhead on every loop iteration. WiFi events are rare so 64 slots is plenty. +// 65 slots = 64 usable (ring buffer reserves one slot to distinguish full from empty) +static LockFreeQueue s_event_queue; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static esp_netif_t *s_sta_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) #ifdef USE_WIFI_AP static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) #endif // USE_WIFI_AP @@ -138,15 +136,11 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi return; } - // copy to heap to keep queue object small + // copy to heap — WiFi events are rare so heap alloc is fine auto *to_send = new IDFWiFiEvent; // NOLINT(cppcoreguidelines-owning-memory) memcpy(to_send, &event, sizeof(IDFWiFiEvent)); - // don't block, we may miss events but the core can handle that - if (xQueueSend(s_event_queue, &to_send, 0L) != pdPASS) { + if (!s_event_queue.push(to_send)) { delete to_send; // NOLINT(cppcoreguidelines-owning-memory) - } else { - // relaxed: this is just a hint; xQueueReceive is the source of truth - s_event_pending.store(1, std::memory_order_relaxed); } } @@ -166,12 +160,6 @@ void WiFiComponent::wifi_pre_setup_() { ESP_LOGE(TAG, "xEventGroupCreate failed"); return; } - // NOLINTNEXTLINE(bugprone-sizeof-expression) - s_event_queue = xQueueCreate(64, sizeof(IDFWiFiEvent *)); - if (s_event_queue == nullptr) { - ESP_LOGE(TAG, "xQueueCreate failed"); - return; - } err = esp_event_loop_create_default(); if (err != ERR_OK) { ESP_LOGE(TAG, "esp_event_loop_create_default failed: %s", esp_err_to_name(err)); @@ -733,26 +721,9 @@ const char *get_disconnect_reason_str(uint8_t reason) { } void WiFiComponent::wifi_loop_() { - // Fast path: skip xQueueReceive kernel call when no events are pending. - // The atomic load is much cheaper than entering the FreeRTOS kernel. - if (s_event_pending.load(std::memory_order_relaxed) == 0) - return; - - // Clear flag before draining. If a new event arrives between the clear - // and xQueueReceive, the flag will be set again and we'll catch it - // next loop iteration. This avoids any counter underflow issues. - s_event_pending.store(0, std::memory_order_relaxed); - - while (true) { - IDFWiFiEvent *data; - if (xQueueReceive(s_event_queue, &data, 0L) != pdTRUE) { - // no event ready - break; - } - - // process event + IDFWiFiEvent *data; + while ((data = s_event_queue.pop()) != nullptr) { wifi_process_event_(data); - delete data; // NOLINT(cppcoreguidelines-owning-memory) } }