From 6dcdefe1799e3596675ea0dabfb2d13aba0e14b4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 4 Apr 2026 11:39:10 -1000 Subject: [PATCH] Move task notification helpers to main_task.c/h, deduplicate ESP32/LibreTiny in wake.h --- esphome/core/lwip_fast_select.c | 37 ---------------------- esphome/core/lwip_fast_select.h | 16 ---------- esphome/core/main_task.c | 36 +++++++++++++++++++++- esphome/core/main_task.h | 21 +++++++++++-- esphome/core/wake.h | 54 +++++++-------------------------- 5 files changed, 64 insertions(+), 100 deletions(-) diff --git a/esphome/core/lwip_fast_select.c b/esphome/core/lwip_fast_select.c index c850e88e20..82b68aa738 100644 --- a/esphome/core/lwip_fast_select.c +++ b/esphome/core/lwip_fast_select.c @@ -127,12 +127,6 @@ #include -// IRAM_ATTR is defined by esp_attr.h (included via FreeRTOS headers) on ESP32. -// On LibreTiny it's not defined — provide a no-op fallback. -#ifndef IRAM_ATTR -#define IRAM_ATTR -#endif - // Compile-time verification of thread safety assumptions. // On ESP32 (Xtensa/RISC-V) and LibreTiny (ARM Cortex-M), naturally-aligned // reads/writes up to 32 bits are atomic. @@ -230,35 +224,4 @@ bool esphome_lwip_set_nodelay(struct lwip_sock *sock, bool enable) { return true; } -// Wake the main loop from another FreeRTOS task. NOT ISR-safe. -void esphome_lwip_wake_main_loop(void) { - TaskHandle_t task = esphome_main_task_handle; - if (task != NULL) { - xTaskNotifyGive(task); - } -} - -// Wake the main loop from an ISR. ISR-safe variant. -void IRAM_ATTR esphome_lwip_wake_main_loop_from_isr(int *px_higher_priority_task_woken) { - TaskHandle_t task = esphome_main_task_handle; - if (task != NULL) { - vTaskNotifyGiveFromISR(task, (BaseType_t *) px_higher_priority_task_woken); - } -} - -// Wake the main loop from any context (ISR, thread, or main loop). -// ESP32-only: uses xPortInIsrContext() to detect ISR context. -// LibreTiny is excluded because it lacks IRAM_ATTR support needed for ISR-safe paths. -#ifdef USE_ESP32 -void IRAM_ATTR esphome_lwip_wake_main_loop_any_context(void) { - if (xPortInIsrContext()) { - int px_higher_priority_task_woken = 0; - esphome_lwip_wake_main_loop_from_isr(&px_higher_priority_task_woken); - portYIELD_FROM_ISR(px_higher_priority_task_woken); - } else { - esphome_lwip_wake_main_loop(); - } -} -#endif - #endif // USE_LWIP_FAST_SELECT diff --git a/esphome/core/lwip_fast_select.h b/esphome/core/lwip_fast_select.h index 96337a1dcd..20ac191673 100644 --- a/esphome/core/lwip_fast_select.h +++ b/esphome/core/lwip_fast_select.h @@ -53,15 +53,6 @@ static inline bool esphome_lwip_socket_has_data(struct lwip_sock *sock) { /// The sock pointer must have been obtained from esphome_lwip_get_sock(). void esphome_lwip_hook_socket(struct lwip_sock *sock); -/// Wake the main loop task from another FreeRTOS task — costs <1 us. -/// NOT ISR-safe — must only be called from task context. -void esphome_lwip_wake_main_loop(void); - -/// Wake the main loop task from an ISR — costs <1 us. -/// ISR-safe variant using vTaskNotifyGiveFromISR(). -/// @param px_higher_priority_task_woken Set to pdTRUE if a context switch is needed. -void esphome_lwip_wake_main_loop_from_isr(int *px_higher_priority_task_woken); - /// Set or clear TCP_NODELAY on a socket's tcp_pcb directly. /// Must be called with the TCPIP core lock held (LwIPLock in C++). /// This bypasses lwip_setsockopt() overhead (socket lookups, switch cascade, @@ -69,13 +60,6 @@ void esphome_lwip_wake_main_loop_from_isr(int *px_higher_priority_task_woken); /// Returns true if successful, false if sock/conn/pcb is NULL or the socket is not TCP. bool esphome_lwip_set_nodelay(struct lwip_sock *sock, bool enable); -/// Wake the main loop task from any context (ISR, thread, or main loop). -/// ESP32-only: uses xPortInIsrContext() to detect ISR context. -/// LibreTiny lacks IRAM_ATTR support needed for ISR-safe paths. -#ifdef USE_ESP32 -void esphome_lwip_wake_main_loop_any_context(void); -#endif - #ifdef __cplusplus } #endif diff --git a/esphome/core/main_task.c b/esphome/core/main_task.c index 52d9c2951a..0addb6ed86 100644 --- a/esphome/core/main_task.c +++ b/esphome/core/main_task.c @@ -1,5 +1,39 @@ #include "esphome/core/main_task.h" #if defined(USE_ESP32) || defined(USE_LIBRETINY) -TaskHandle_t esphome_main_task_handle = NULL; + +// IRAM_ATTR is defined by esp_attr.h (included via FreeRTOS headers) on ESP32. +// On LibreTiny it's not reliably available — provide a no-op fallback. +#ifndef IRAM_ATTR +#define IRAM_ATTR #endif + +TaskHandle_t esphome_main_task_handle = NULL; + +void esphome_main_task_notify(void) { + TaskHandle_t task = esphome_main_task_handle; + if (task != NULL) { + xTaskNotifyGive(task); + } +} + +void IRAM_ATTR esphome_main_task_notify_from_isr(int *px_higher_priority_task_woken) { + TaskHandle_t task = esphome_main_task_handle; + if (task != NULL) { + vTaskNotifyGiveFromISR(task, (BaseType_t *) px_higher_priority_task_woken); + } +} + +#ifdef USE_ESP32 +void IRAM_ATTR esphome_main_task_notify_any_context(void) { + if (xPortInIsrContext()) { + int px_higher_priority_task_woken = 0; + esphome_main_task_notify_from_isr(&px_higher_priority_task_woken); + portYIELD_FROM_ISR(px_higher_priority_task_woken); + } else { + esphome_main_task_notify(); + } +} +#endif + +#endif // USE_ESP32 || USE_LIBRETINY diff --git a/esphome/core/main_task.h b/esphome/core/main_task.h index d46a54cb19..4223dec3ec 100644 --- a/esphome/core/main_task.h +++ b/esphome/core/main_task.h @@ -1,12 +1,14 @@ #pragma once -/// Main loop task handle — shared between wake.h (C++) and lwip_fast_select.c (C). -/// Set once during Application::setup() via xTaskGetCurrentTaskHandle(). +/// Main loop task handle and wake helpers — shared between wake.h (C++) and lwip_fast_select.c (C). +/// esphome_main_task_handle is set once during Application::setup() via xTaskGetCurrentTaskHandle(). + +#if defined(USE_ESP32) || defined(USE_LIBRETINY) #ifdef USE_ESP32 #include #include -#elif defined(USE_LIBRETINY) +#else #include #include #endif @@ -17,6 +19,19 @@ extern "C" { extern TaskHandle_t esphome_main_task_handle; +/// Wake the main loop task from another FreeRTOS task. NOT ISR-safe. +void esphome_main_task_notify(void); + +/// Wake the main loop task from an ISR. ISR-safe. +void esphome_main_task_notify_from_isr(int *px_higher_priority_task_woken); + +#ifdef USE_ESP32 +/// Wake the main loop from any context (ISR or task). ESP32-only (needs xPortInIsrContext). +void esphome_main_task_notify_any_context(void); +#endif + #ifdef __cplusplus } #endif + +#endif // USE_ESP32 || USE_LIBRETINY diff --git a/esphome/core/wake.h b/esphome/core/wake.h index 79037c8264..1b2905934d 100644 --- a/esphome/core/wake.h +++ b/esphome/core/wake.h @@ -25,59 +25,27 @@ namespace esphome { extern volatile bool g_main_loop_woke; #endif -// === ESP32 === -#if defined(USE_ESP32) +// === ESP32 / LibreTiny (FreeRTOS) === +#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#ifdef USE_ESP32 /// Inline impl — ISR callers inline this into IRAM. inline void ESPHOME_ALWAYS_INLINE wake_loop_isrsafe_inline_(int *px_higher_priority_task_woken) { - if (esphome_main_task_handle != nullptr) - vTaskNotifyGiveFromISR(esphome_main_task_handle, (BaseType_t *) px_higher_priority_task_woken); + esphome_main_task_notify_from_isr(px_higher_priority_task_woken); } /// IRAM_ATTR entry point — defined in wake.cpp. void wake_loop_isrsafe(int *px_higher_priority_task_woken); -/// Inline impl — ISR callers inline this into IRAM. Uses xPortInIsrContext() to pick safe API. -inline void ESPHOME_ALWAYS_INLINE wake_loop_any_context_inline_() { - if (esphome_main_task_handle == nullptr) - return; - if (xPortInIsrContext()) { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - vTaskNotifyGiveFromISR(esphome_main_task_handle, &xHigherPriorityTaskWoken); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); - } else { - xTaskNotifyGive(esphome_main_task_handle); - } -} +/// Inline impl — ISR callers inline this into IRAM. +inline void ESPHOME_ALWAYS_INLINE wake_loop_any_context_inline_() { esphome_main_task_notify_any_context(); } /// IRAM_ATTR entry point — defined in wake.cpp. void wake_loop_any_context(); +#else +/// LibreTiny: no working IRAM_ATTR, no ISR callers. +inline void wake_loop_any_context() { esphome_main_task_notify(); } +#endif -inline void wake_loop_threadsafe() { - if (esphome_main_task_handle != nullptr) - xTaskNotifyGive(esphome_main_task_handle); -} - -namespace internal { -inline void wakeable_delay(uint32_t ms) { - if (ms == 0) { - yield(); - return; - } - ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(ms)); -} -} // namespace internal - -// === LibreTiny === -#elif defined(USE_LIBRETINY) - -inline void wake_loop_any_context() { - if (esphome_main_task_handle != nullptr) - xTaskNotifyGive(esphome_main_task_handle); -} - -inline void wake_loop_threadsafe() { - if (esphome_main_task_handle != nullptr) - xTaskNotifyGive(esphome_main_task_handle); -} +inline void wake_loop_threadsafe() { esphome_main_task_notify(); } namespace internal { inline void wakeable_delay(uint32_t ms) {