From 5f04cff8bc043aced5f91c1535663a19c30934b0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 10 Apr 2026 17:01:07 -1000 Subject: [PATCH] [esphome.ota] Fold OTA wake hook into wake.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All wake_* state lives in one place now. wake.h gains a C-compatible section at the top (the inline esphome_wake_ota_component_any_context() + its two extern 'volatile bool *' globals) guarded outside any C++ namespace, with the existing C++ platform wake primitives moved behind an outer #ifdef __cplusplus. lwip_fast_select.c includes wake.h directly for the inline; .cpp files continue to see the C++ side as before. Deletes the ephemeral esphome/core/ota_wake_hook.h — same code, better home. --- .../components/socket/lwip_raw_tcp_impl.cpp | 6 +- esphome/core/application.cpp | 17 +++--- esphome/core/application.h | 12 ++-- esphome/core/lwip_fast_select.c | 2 +- esphome/core/ota_wake_hook.h | 45 -------------- esphome/core/wake.h | 59 +++++++++++++++++++ 6 files changed, 78 insertions(+), 63 deletions(-) delete mode 100644 esphome/core/ota_wake_hook.h diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index b35338a2bf..b6e5730b66 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -11,7 +11,7 @@ #include "esphome/core/wake.h" #include "esphome/core/log.h" #ifdef USE_OTA -#include "esphome/core/ota_wake_hook.h" +#include "esphome/core/wake.h" // inline esphome_wake_ota_component_any_context() lives here #endif #ifdef USE_ESP8266 @@ -861,8 +861,8 @@ err_t LWIPRawListenImpl::accept_fn_(struct tcp_pcb *newpcb, err_t err) { // Mark the OTA component loop to be re-enabled if it disabled itself while idle. // This MUST happen before wake_loop_any_context() below — otherwise the main loop // could wake, run a full iteration, and finish before we set the pending-enable - // flags, losing the wake event. Inline hook (ota_wake_hook.h) — two volatile stores, - // no function call. Safe from RP2040's low-priority user IRQ context. + // flags, losing the wake event. Inline hook (wake.h) — two volatile stores, no + // function call. Safe from RP2040's low-priority user IRQ context. esphome_wake_ota_component_any_context(); #endif // Wake the main loop immediately so it can accept the new connection. diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index b2b2e0fdbe..21d3ef4494 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -2,9 +2,7 @@ #include "esphome/core/build_info_data.h" #include "esphome/core/log.h" #include "esphome/core/progmem.h" -#ifdef USE_OTA -#include "esphome/core/ota_wake_hook.h" -#endif +#include "esphome/core/wake.h" #include #ifdef USE_ESP8266 @@ -452,16 +450,19 @@ void Application::enable_pending_loops_() { } } -#ifdef USE_OTA -// Storage for the inline OTA wake hook (see esphome/core/ota_wake_hook.h). Set in -// Application::set_ota_wake_component() and read from the lwip fast-select callback on -// every NETCONN_EVT_RCVPLUS. Kept as raw C globals so the .c file can inline the wake -// body (two volatile stores) without a function-call round trip into application.cpp. +// Storage for the inline OTA wake hook (see esphome/core/wake.h). Set in +// Application::set_ota_wake_component() and read from the lwip fast-select callback +// on every NETCONN_EVT_RCVPLUS. Kept as raw C globals so the .c file can inline the +// wake body (two volatile stores) without a function-call round trip. Defined +// unconditionally so wake.h's inline compiles the same whether or not OTA is in the +// build — when OTA is absent nothing ever calls set_ota_wake_component() and the +// pointers stay nullptr, collapsing the inline to a single null check. extern "C" { volatile bool *esphome_ota_pending_enable_loop_ptr = nullptr; volatile bool *esphome_ota_has_pending_requests_ptr = nullptr; } +#ifdef USE_OTA void Application::set_ota_wake_component(Component *component) { // Application is a friend of Component — can take the address of its protected // pending_enable_loop_ field. The C-side inline hook writes through that pointer when diff --git a/esphome/core/application.h b/esphome/core/application.h index 48fc620cdc..baf8e21a99 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -561,12 +561,12 @@ class Application { #ifdef USE_OTA /// Register the OTA component so socket-wake paths can enable its loop when a new - /// connection arrives on the listening socket. Captures the address of the component's - /// pending_enable_loop_ flag and the Application has_pending_enable_loop_requests_ flag - /// into extern C globals consumed by the inline wake hook in ota_wake_hook.h. Defined - /// out-of-line in application.cpp so application.h doesn't need to pull in the hook - /// header. OTA calls this once from setup(); the component itself then self-disables - /// its loop on its first idle tick. + /// connection arrives on the listening socket. Captures the addresses of the component's + /// pending_enable_loop_ flag and Application's has_pending_enable_loop_requests_ flag + /// into extern-C globals consumed by the inline wake hook in wake.h. Defined out-of-line + /// in application.cpp so application.h doesn't need to pull in wake.h. OTA calls this + /// once from setup(); the component itself then self-disables its loop on its first + /// idle tick. void set_ota_wake_component(Component *component); #endif diff --git a/esphome/core/lwip_fast_select.c b/esphome/core/lwip_fast_select.c index f0cafc2dbe..061fafbc51 100644 --- a/esphome/core/lwip_fast_select.c +++ b/esphome/core/lwip_fast_select.c @@ -125,7 +125,7 @@ #include "esphome/core/lwip_fast_select.h" #include "esphome/core/main_task.h" #ifdef ESPHOME_USE_OTA -#include "esphome/core/ota_wake_hook.h" +#include "esphome/core/wake.h" // inline esphome_wake_ota_component_any_context() lives here #endif #include diff --git a/esphome/core/ota_wake_hook.h b/esphome/core/ota_wake_hook.h deleted file mode 100644 index 06ed1623df..0000000000 --- a/esphome/core/ota_wake_hook.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -// Inline OTA wake hook, called from lwip_fast_select.c on every NETCONN_EVT_RCVPLUS so a -// disabled OTA loop can be re-enabled when a monitored socket signals activity. -// -// Defined as a static inline here (rather than an out-of-line extern "C" shim into -// application.cpp) so the fast-select callback pays zero function-call overhead per -// socket event: the two volatile stores below are cheaper inlined than dispatched. -// -// The two pointers are set once in Application::set_ota_wake_component() to the addresses -// of Component::pending_enable_loop_ and Application::has_pending_enable_loop_requests_. -// Accessing those C++ members by raw address is safe: Application is a friend of Component -// (granting access at registration time), and volatile writes through a bool* see the same -// storage the C++ side reads. - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// Address of the registered OTA component's pending_enable_loop_ flag. NULL until -// Application::set_ota_wake_component() is called. When non-NULL, the has-pending -// pointer below is also non-NULL, so a single null check covers both. -extern volatile bool *esphome_ota_pending_enable_loop_ptr; -// Address of Application::has_pending_enable_loop_requests_. Set in tandem with the -// pending_enable pointer above. -extern volatile bool *esphome_ota_has_pending_requests_ptr; - -// Mark the registered OTA component pending loop-enable. Safe to call from LwIP TCP/IP -// task context and raw-TCP IRQ context — only writes to volatile bools, no locks. -// Callers must invoke this BEFORE waking the main task, so the flags are visible to the -// main loop's next iteration. -static inline void esphome_wake_ota_component_any_context(void) { - volatile bool *pending_enable = esphome_ota_pending_enable_loop_ptr; - if (pending_enable != NULL) { - *pending_enable = true; - *esphome_ota_has_pending_requests_ptr = true; - } -} - -#ifdef __cplusplus -} -#endif diff --git a/esphome/core/wake.h b/esphome/core/wake.h index a8c9b7ad08..9ad761e8aa 100644 --- a/esphome/core/wake.h +++ b/esphome/core/wake.h @@ -3,6 +3,63 @@ /// @file wake.h /// Platform-specific main loop wake primitives. /// Always available on all platforms — no opt-in needed. +/// +/// This file has two sections: +/// 1. A C-compatible section at the top (the inline OTA wake hook) that .c files +/// like lwip_fast_select.c can include. Uses only /. +/// 2. A C++ section (everything after #ifdef __cplusplus below) with the existing +/// platform wake primitives and wakeable_delay() implementations. + +// ============================================================================ +// C-compatible section: inline OTA wake hook +// ============================================================================ +// +// Called from lwip_fast_select.c (and lwip_raw_tcp_impl.cpp / host select) on every +// NETCONN_EVT_RCVPLUS so a disabled OTA loop can be re-enabled when a monitored +// socket signals activity. Static inline (not an extern-C trampoline) so the .c +// callback pays zero function-call overhead — just a pointer load, null check, and +// two volatile bool stores inlined into the callback body. +// +// The two pointers are captured once in Application::set_ota_wake_component(): they +// point at Component::pending_enable_loop_ and Application::has_pending_enable_loop_requests_. +// When OTA is not compiled into the build, nothing calls set_ota_wake_component(), +// the pointers stay NULL, and the inline collapses to a single null check. + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Address of the registered OTA component's pending_enable_loop_ flag, captured at +// registration time. NULL when no OTA component has registered (including when OTA +// is not compiled in at all). +extern volatile bool *esphome_ota_pending_enable_loop_ptr; +// Address of Application::has_pending_enable_loop_requests_. Set in tandem with the +// pending_enable pointer above — single null check covers both. +extern volatile bool *esphome_ota_has_pending_requests_ptr; + +// Mark the registered OTA component pending loop-enable. Safe from the LwIP TCP/IP +// task and raw-TCP IRQ context — only volatile stores, no locks. Callers MUST invoke +// this BEFORE waking the main task, so the flags are visible on the next iteration. +static inline void esphome_wake_ota_component_any_context(void) { + volatile bool *pending_enable = esphome_ota_pending_enable_loop_ptr; + if (pending_enable != NULL) { + *pending_enable = true; + *esphome_ota_has_pending_requests_ptr = true; + } +} + +#ifdef __cplusplus +} // extern "C" +#endif + +// ============================================================================ +// C++ section: platform wake primitives +// ============================================================================ + +#ifdef __cplusplus #include "esphome/core/defines.h" #include "esphome/core/hal.h" @@ -124,3 +181,5 @@ inline void wakeable_delay(uint32_t ms) { #endif } // namespace esphome + +#endif // __cplusplus