From 8b81cf3fda52db985a428b9f7962ad12903e1e9f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 31 Mar 2026 20:39:21 -1000 Subject: [PATCH] [wifi] Use queue abstraction for LibreTiny WiFi events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace static FreeRTOS queue globals in the LibreTiny WiFi component with the same queue abstraction used by ESP32: - ESPHOME_THREAD_MULTI_ATOMICS (RTL87xx, LN882x): LockFreeQueue - ESPHOME_THREAD_MULTI_NO_ATOMICS (BK72xx): new FreeRTOSQueue wrapper Add FreeRTOSQueue in freertos_queue.h — an xQueue wrapper providing the same API as LockFreeQueue (push, pop, get_and_reset_dropped_count) for platforms without hardware atomic instructions. The event queue is now a class member instead of a static global, matching the ESP32 pattern. --- esphome/components/wifi/wifi_component.h | 16 ++++ .../wifi/wifi_component_libretiny.cpp | 47 ++--------- esphome/core/freertos_queue.h | 82 +++++++++++++++++++ 3 files changed, 107 insertions(+), 38 deletions(-) create mode 100644 esphome/core/freertos_queue.h diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 665dec37d5..4f5ed0bc9f 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -9,6 +9,11 @@ #ifdef USE_ESP32 #include "esphome/core/lock_free_queue.h" #endif +#if defined(USE_LIBRETINY) && defined(ESPHOME_THREAD_MULTI_ATOMICS) +#include "esphome/core/lock_free_queue.h" +#elif defined(USE_LIBRETINY) && defined(ESPHOME_THREAD_MULTI_NO_ATOMICS) +#include "esphome/core/freertos_queue.h" +#endif #include "esphome/core/string_ref.h" #include @@ -882,6 +887,17 @@ class WiFiComponent final : public Component { LockFreeQueue event_queue_; #endif +#ifdef USE_LIBRETINY + // Thread-safe queue for WiFi events from LibreTiny callback thread. + // LockFreeQueue on platforms with hardware atomics (RTL87xx, LN882x), + // FreeRTOSQueue on platforms without (BK72xx). +#ifdef ESPHOME_THREAD_MULTI_ATOMICS + LockFreeQueue event_queue_; +#else + FreeRTOSQueue event_queue_; +#endif +#endif + private: // Stores a pointer to a string literal (static storage duration). // ONLY set from Python-generated code with string literals - never dynamic strings. diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 9565ffa747..479030d442 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -10,9 +10,6 @@ #include "lwip/err.h" #include "lwip/dns.h" -#include -#include - #ifdef USE_BK72XX extern "C" { #include @@ -43,16 +40,13 @@ static const char *const TAG = "wifi_lt"; // (like connection status flags) from the callback causes race conditions: // - The main loop may never see state changes (values cached in registers) // - State changes may be visible in inconsistent order -// - LibreTiny targets (BK7231, RTL8720) lack atomic instructions (no LDREX/STREX) // // Solution: Queue events in the callback and process them in the main loop. // This is the same approach used by ESP32 IDF's wifi_process_event_(). // All state modifications happen in the main loop context, eliminating races. - -static constexpr size_t EVENT_QUEUE_SIZE = 16; // Max pending WiFi events before overflow -static QueueHandle_t s_event_queue = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static volatile uint32_t s_event_queue_overflow_count = - 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +// +// On platforms with hardware atomics (RTL87xx, LN882x): LockFreeQueue (SPSC ring buffer) +// On platforms without (BK72xx): FreeRTOSQueue (xQueue wrapper with critical sections) // Event structure for queued WiFi events - contains a copy of event data // to avoid lifetime issues with the original event data from the callback @@ -352,10 +346,6 @@ using esphome_wifi_event_info_t = arduino_event_info_t; // Event callback - runs in WiFi driver thread context // Only queues events for processing in main loop, no logging or state changes here void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_wifi_event_info_t info) { - if (s_event_queue == nullptr) { - return; - } - // Allocate on heap and fill directly to avoid extra memcpy auto *to_send = new LTWiFiEvent{}; // NOLINT(cppcoreguidelines-owning-memory) to_send->event_id = event; @@ -428,9 +418,8 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } // Queue event (don't block if queue is full) - if (xQueueSend(s_event_queue, &to_send, 0) != pdPASS) { + if (!this->event_queue_.push(to_send)) { delete to_send; // NOLINT(cppcoreguidelines-owning-memory) - s_event_queue_overflow_count++; } } @@ -620,14 +609,6 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { } } void WiFiComponent::wifi_pre_setup_() { - // Create event queue for thread-safe event handling - // Events are pushed from WiFi callback thread and processed in main loop - s_event_queue = xQueueCreate(EVENT_QUEUE_SIZE, sizeof(LTWiFiEvent *)); - if (s_event_queue == nullptr) { - ESP_LOGE(TAG, "Failed to create event queue"); - return; - } - WiFi.onEvent( [this](arduino_event_id_t event, arduino_event_info_t info) { this->wifi_event_callback_(event, info); }); // Make sure WiFi is in clean state before anything starts @@ -797,24 +778,14 @@ network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask( network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } void WiFiComponent::wifi_loop_() { - // Process all pending events from the queue - if (s_event_queue == nullptr) { - return; - } - // Check for dropped events due to queue overflow - if (s_event_queue_overflow_count > 0) { - ESP_LOGW(TAG, "Event queue overflow, %" PRIu32 " events dropped", s_event_queue_overflow_count); - s_event_queue_overflow_count = 0; + uint16_t dropped = this->event_queue_.get_and_reset_dropped_count(); + if (dropped > 0) { + ESP_LOGW(TAG, "Dropped %" PRIu16 " WiFi events due to buffer overflow", dropped); } - while (true) { - LTWiFiEvent *event; - if (xQueueReceive(s_event_queue, &event, 0) != pdTRUE) { - // No more events - break; - } - + LTWiFiEvent *event; + while ((event = this->event_queue_.pop()) != nullptr) { wifi_process_event_(event); delete event; // NOLINT(cppcoreguidelines-owning-memory) } diff --git a/esphome/core/freertos_queue.h b/esphome/core/freertos_queue.h new file mode 100644 index 0000000000..e65d2fbbb1 --- /dev/null +++ b/esphome/core/freertos_queue.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +#include +#include + +/* + * FreeRTOS queue wrapper for single-producer single-consumer scenarios on + * platforms without hardware atomic support (e.g. BK72xx ARM968E-S). + * + * Provides the same API as LockFreeQueue (push, pop, get_and_reset_dropped_count, + * empty, full, size) but uses xQueue internally, which synchronizes via + * FreeRTOS critical sections. + * + * @tparam T The type of elements stored in the queue (stored as pointers) + * @tparam SIZE The maximum number of elements + */ + +namespace esphome { + +template class FreeRTOSQueue { + public: + FreeRTOSQueue() : dropped_count_(0) { this->handle_ = xQueueCreate(SIZE, sizeof(T *)); } + + bool push(T *element) { + if (element == nullptr || this->handle_ == nullptr) + return false; + + if (xQueueSend(this->handle_, &element, 0) != pdPASS) { + this->dropped_count_++; + return false; + } + return true; + } + + T *pop() { + if (this->handle_ == nullptr) + return nullptr; + + T *element; + if (xQueueReceive(this->handle_, &element, 0) != pdTRUE) { + return nullptr; + } + return element; + } + + uint16_t get_and_reset_dropped_count() { + uint16_t count = this->dropped_count_; + if (count == 0) + return 0; + this->dropped_count_ = 0; + return count; + } + + void increment_dropped_count() { this->dropped_count_++; } + + bool empty() const { + if (this->handle_ == nullptr) + return true; + return uxQueueMessagesWaiting(this->handle_) == 0; + } + + bool full() const { + if (this->handle_ == nullptr) + return true; + return uxQueueSpacesAvailable(this->handle_) == 0; + } + + size_t size() const { + if (this->handle_ == nullptr) + return 0; + return uxQueueMessagesWaiting(this->handle_); + } + + protected: + QueueHandle_t handle_; + volatile uint16_t dropped_count_; +}; + +} // namespace esphome