mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 08:55:36 +00:00
[esphome.ota] Inline fast-select wake hook via ota_wake_hook.h
The extern C shim esphome_wake_ota_component_any_context() was an out-of-line call from lwip_fast_select.c into application.cpp: save registers, call, prologue, two stores, epilogue, ret. Per-RCVPLUS, that's ~10-15 Xtensa cycles of pure call overhead on top of the two volatile bool stores the shim actually does. Move the body into a new C-compatible header (esphome/core/ota_wake_hook.h) as a static inline, backed by two extern C 'volatile bool *' globals that point at Component::pending_enable_loop_ and Application::has_pending_enable_loop_requests_. set_ota_wake_component() captures the addresses once at registration time; the fast-select callback then inlines a null-check + two volatile stores with zero call overhead. The main loop sees the same two flags it already checks every iteration (has_pending_enable_loop_requests_ gates enable_pending_loops_, which iterates the inactive section looking for components with pending_enable_loop_ set). Zero new main-loop work — the inline hook writes exactly the state enable_loop_soon_any_context() would have written. RAM change: -4 bytes on Application (ota_wake_component_ field removed) plus +8 bytes in BSS for the two extern pointers. Net +4 bytes RAM. Raw-TCP and HOST paths switched from App.wake_ota_component_any_context() to the inline hook too.
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
#include "esphome/core/wake.h"
|
||||
#include "esphome/core/log.h"
|
||||
#ifdef USE_OTA
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/ota_wake_hook.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
@@ -861,9 +861,9 @@ 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. Safe from RP2040's low-priority user IRQ context:
|
||||
// it only writes volatile bools, no heap or locks.
|
||||
esphome::App.wake_ota_component_any_context();
|
||||
// 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.
|
||||
esphome_wake_ota_component_any_context();
|
||||
#endif
|
||||
// Wake the main loop immediately so it can accept the new connection.
|
||||
esphome::wake_loop_any_context();
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#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 <cstring>
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
@@ -449,14 +452,24 @@ void Application::enable_pending_loops_() {
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(USE_OTA) && defined(USE_LWIP_FAST_SELECT)
|
||||
// Called from the LwIP TCP/IP task via esphome_socket_event_callback() on NETCONN_EVT_RCVPLUS,
|
||||
// BEFORE that callback calls xTaskNotifyGive() — pending-enable flags must be visible before
|
||||
// the main task wakes, or the main loop can run a full iteration without seeing the request.
|
||||
// Only marks the OTA component as pending loop-enable; does not itself wake the main task.
|
||||
// OTA's __init__.py emits USE_OTA as both a cg.add_define (for static analyzers) AND a
|
||||
// cg.add_build_flag (so the .c fast-select file also sees it).
|
||||
extern "C" void esphome_wake_ota_component_any_context() { App.wake_ota_component_any_context(); }
|
||||
#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.
|
||||
extern "C" {
|
||||
volatile bool *esphome_ota_pending_enable_loop_ptr = nullptr;
|
||||
volatile bool *esphome_ota_has_pending_requests_ptr = nullptr;
|
||||
}
|
||||
|
||||
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
|
||||
// any monitored socket fires RCVPLUS, making the flags visible to enable_pending_loops_()
|
||||
// on the next main loop iteration.
|
||||
esphome_ota_pending_enable_loop_ptr = &component->pending_enable_loop_;
|
||||
esphome_ota_has_pending_requests_ptr = &this->has_pending_enable_loop_requests_;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
@@ -566,11 +579,10 @@ void Application::yield_with_select_(uint32_t delay_ms) {
|
||||
if (ret >= 0) [[likely]] {
|
||||
#ifdef USE_OTA
|
||||
// Dead code today — host does not currently support the esphome OTA platform,
|
||||
// so ota_wake_component_ is never set and this call is a no-op. Kept so the
|
||||
// wake path works out of the box if host ever gains OTA support. Harmless
|
||||
// cost: a single nullptr check per select() return.
|
||||
// so the inline hook's pointers are always NULL and this is a no-op. Kept so the
|
||||
// wake path works out of the box if host ever gains OTA support.
|
||||
if (ret > 0) {
|
||||
this->wake_ota_component_any_context();
|
||||
esphome_wake_ota_component_any_context();
|
||||
}
|
||||
#endif
|
||||
// Yield if zero timeout since select(0) only polls without yielding
|
||||
|
||||
@@ -560,23 +560,14 @@ class Application {
|
||||
static void IRAM_ATTR wake_loop_any_context() { esphome::wake_loop_any_context(); }
|
||||
|
||||
#ifdef USE_OTA
|
||||
/// Register the OTA component so socket-wake paths can enable its loop when
|
||||
/// a new connection arrives on the listening socket. OTA disables its own
|
||||
/// loop while idle to avoid per-tick dispatch overhead.
|
||||
void set_ota_wake_component(Component *component) { this->ota_wake_component_ = component; }
|
||||
/// Mark the registered OTA component (if any) for loop re-enable. Intentionally
|
||||
/// does NOT call wake_loop_any_context() — every caller (lwip fast-select
|
||||
/// callback, raw-tcp accept callback, host select() return path) has already
|
||||
/// woken the main loop, so a second wake here would be redundant. Application
|
||||
/// is a friend of Component, so we set the pending-enable flags directly.
|
||||
/// Not IRAM_ATTR: all callers run in task / user-IRQ context, not a real ISR,
|
||||
/// and the LwIP event callbacks that invoke this are not IRAM-resident either.
|
||||
void wake_ota_component_any_context() {
|
||||
if (this->ota_wake_component_ != nullptr) {
|
||||
this->ota_wake_component_->pending_enable_loop_ = true;
|
||||
this->has_pending_enable_loop_requests_ = true;
|
||||
}
|
||||
}
|
||||
/// 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.
|
||||
void set_ota_wake_component(Component *component);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
@@ -654,9 +645,6 @@ class Application {
|
||||
|
||||
// Pointer-sized members first
|
||||
Component *current_component_{nullptr};
|
||||
#ifdef USE_OTA
|
||||
Component *ota_wake_component_{nullptr}; // Set by ESPHomeOTAComponent to receive socket-wake notifications
|
||||
#endif
|
||||
|
||||
// std::vector (3 pointers each: begin, end, capacity)
|
||||
// Partitioned vector design for looping components
|
||||
|
||||
@@ -124,6 +124,9 @@
|
||||
|
||||
#include "esphome/core/lwip_fast_select.h"
|
||||
#include "esphome/core/main_task.h"
|
||||
#ifdef ESPHOME_USE_OTA
|
||||
#include "esphome/core/ota_wake_hook.h"
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#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 <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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
|
||||
Reference in New Issue
Block a user