mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 17:05:36 +00:00
[core] Cache lwip_sock pointers to simplify fast select hot path
Cache struct lwip_sock* pointers at socket registration time instead of looking them up by fd on every iteration. The previous code stored file descriptors and called lwip_socket_dbg_get_socket(fd) on every loop iteration to convert each fd back to a pointer — only to read a single field from it. - register_socket(lwip_sock*) returns bool and hooks the callback internally — replaces the separate register_socket_fd + hook dance - Hot loop iterates struct lwip_sock* directly instead of converting fds each iteration - Socket::ready() uses the cached pointer instead of fd lookup - esphome_lwip_hook_socket() takes struct lwip_sock* directly - esphome_lwip_socket_has_data() moved to header as static inline for inlining — uses offset-based access verified by _Static_assert - Null cached_sock_ on close to prevent post-close dereference - socket_ready() tolerant of null pointer for safety
This commit is contained in:
@@ -11,11 +11,15 @@ namespace esphome::socket {
|
||||
|
||||
BSDSocketImpl::BSDSocketImpl(int fd, bool monitor_loop) {
|
||||
this->fd_ = fd;
|
||||
// Register new socket with the application for select() if monitoring requested
|
||||
if (monitor_loop && this->fd_ >= 0) {
|
||||
// Only set loop_monitored_ to true if registration succeeds
|
||||
this->loop_monitored_ = App.register_socket_fd(this->fd_);
|
||||
}
|
||||
if (!monitor_loop || this->fd_ < 0)
|
||||
return;
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
// Cache lwip_sock pointer and register for monitoring (hooks callback internally)
|
||||
this->cached_sock_ = esphome_lwip_get_sock(this->fd_);
|
||||
this->loop_monitored_ = App.register_socket(this->cached_sock_);
|
||||
#else
|
||||
this->loop_monitored_ = App.register_socket_fd(this->fd_);
|
||||
#endif
|
||||
}
|
||||
|
||||
BSDSocketImpl::~BSDSocketImpl() {
|
||||
@@ -26,10 +30,17 @@ BSDSocketImpl::~BSDSocketImpl() {
|
||||
|
||||
int BSDSocketImpl::close() {
|
||||
if (!this->closed_) {
|
||||
// Unregister from select() before closing if monitored
|
||||
// Unregister before closing to avoid dangling pointer in monitored set
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
if (this->loop_monitored_) {
|
||||
App.unregister_socket(this->cached_sock_);
|
||||
this->cached_sock_ = nullptr;
|
||||
}
|
||||
#else
|
||||
if (this->loop_monitored_) {
|
||||
App.unregister_socket_fd(this->fd_);
|
||||
}
|
||||
#endif
|
||||
int ret = ::close(this->fd_);
|
||||
this->closed_ = true;
|
||||
return ret;
|
||||
@@ -48,7 +59,11 @@ int BSDSocketImpl::setblocking(bool blocking) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
bool BSDSocketImpl::ready() const { return socket_ready(this->cached_sock_, this->loop_monitored_); }
|
||||
#else
|
||||
bool BSDSocketImpl::ready() const { return socket_ready_fd(this->fd_, this->loop_monitored_); }
|
||||
#endif
|
||||
|
||||
size_t BSDSocketImpl::getpeername_to(std::span<char, SOCKADDR_STR_LEN> buf) {
|
||||
struct sockaddr_storage storage;
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
#include <lwip/sockets.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
struct lwip_sock;
|
||||
#endif
|
||||
|
||||
namespace esphome::socket {
|
||||
|
||||
class BSDSocketImpl {
|
||||
@@ -105,6 +109,9 @@ class BSDSocketImpl {
|
||||
|
||||
protected:
|
||||
int fd_{-1};
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
struct lwip_sock *cached_sock_{nullptr}; // Cached for direct rcvevent read in ready()
|
||||
#endif
|
||||
bool closed_{false};
|
||||
bool loop_monitored_{false};
|
||||
};
|
||||
|
||||
@@ -11,11 +11,15 @@ namespace esphome::socket {
|
||||
|
||||
LwIPSocketImpl::LwIPSocketImpl(int fd, bool monitor_loop) {
|
||||
this->fd_ = fd;
|
||||
// Register new socket with the application for select() if monitoring requested
|
||||
if (monitor_loop && this->fd_ >= 0) {
|
||||
// Only set loop_monitored_ to true if registration succeeds
|
||||
this->loop_monitored_ = App.register_socket_fd(this->fd_);
|
||||
}
|
||||
if (!monitor_loop || this->fd_ < 0)
|
||||
return;
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
// Cache lwip_sock pointer and register for monitoring (hooks callback internally)
|
||||
this->cached_sock_ = esphome_lwip_get_sock(this->fd_);
|
||||
this->loop_monitored_ = App.register_socket(this->cached_sock_);
|
||||
#else
|
||||
this->loop_monitored_ = App.register_socket_fd(this->fd_);
|
||||
#endif
|
||||
}
|
||||
|
||||
LwIPSocketImpl::~LwIPSocketImpl() {
|
||||
@@ -26,10 +30,17 @@ LwIPSocketImpl::~LwIPSocketImpl() {
|
||||
|
||||
int LwIPSocketImpl::close() {
|
||||
if (!this->closed_) {
|
||||
// Unregister from select() before closing if monitored
|
||||
// Unregister before closing to avoid dangling pointer in monitored set
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
if (this->loop_monitored_) {
|
||||
App.unregister_socket(this->cached_sock_);
|
||||
this->cached_sock_ = nullptr;
|
||||
}
|
||||
#else
|
||||
if (this->loop_monitored_) {
|
||||
App.unregister_socket_fd(this->fd_);
|
||||
}
|
||||
#endif
|
||||
int ret = lwip_close(this->fd_);
|
||||
this->closed_ = true;
|
||||
return ret;
|
||||
@@ -48,7 +59,11 @@ int LwIPSocketImpl::setblocking(bool blocking) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
bool LwIPSocketImpl::ready() const { return socket_ready(this->cached_sock_, this->loop_monitored_); }
|
||||
#else
|
||||
bool LwIPSocketImpl::ready() const { return socket_ready_fd(this->fd_, this->loop_monitored_); }
|
||||
#endif
|
||||
|
||||
size_t LwIPSocketImpl::getpeername_to(std::span<char, SOCKADDR_STR_LEN> buf) {
|
||||
struct sockaddr_storage storage;
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "headers.h"
|
||||
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
struct lwip_sock;
|
||||
#endif
|
||||
|
||||
namespace esphome::socket {
|
||||
|
||||
class LwIPSocketImpl {
|
||||
@@ -71,6 +75,9 @@ class LwIPSocketImpl {
|
||||
|
||||
protected:
|
||||
int fd_{-1};
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
struct lwip_sock *cached_sock_{nullptr}; // Cached for direct rcvevent read in ready()
|
||||
#endif
|
||||
bool closed_{false};
|
||||
bool loop_monitored_{false};
|
||||
};
|
||||
|
||||
@@ -8,7 +8,13 @@
|
||||
|
||||
namespace esphome::socket {
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
// Shared ready() implementation for fd-based socket implementations (BSD and LWIP sockets).
|
||||
// Uses cached lwip_sock pointer for direct rcvevent read — no fd lookup overhead.
|
||||
bool socket_ready(struct lwip_sock *cached_sock, bool loop_monitored) {
|
||||
return !loop_monitored || (cached_sock != nullptr && esphome_lwip_socket_has_data(cached_sock));
|
||||
}
|
||||
#elif defined(USE_SOCKET_SELECT_SUPPORT)
|
||||
// Shared ready() implementation for fd-based socket implementations (BSD and LWIP sockets).
|
||||
// Checks if the Application's select() loop has marked this fd as ready.
|
||||
bool socket_ready_fd(int fd, bool loop_monitored) { return !loop_monitored || App.is_socket_ready_(fd); }
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#include "esphome/core/optional.h"
|
||||
#include "headers.h"
|
||||
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
struct lwip_sock;
|
||||
#endif
|
||||
|
||||
#if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS)
|
||||
|
||||
// Include only the active implementation's header.
|
||||
@@ -36,7 +40,10 @@ using Socket = LWIPRawImpl;
|
||||
using ListenSocket = LWIPRawListenImpl;
|
||||
#endif
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
/// Shared ready() helper using cached lwip_sock pointer for direct rcvevent read.
|
||||
bool socket_ready(struct lwip_sock *cached_sock, bool loop_monitored);
|
||||
#elif defined(USE_SOCKET_SELECT_SUPPORT)
|
||||
/// Shared ready() helper for fd-based socket implementations.
|
||||
/// Checks if the Application's select() loop has marked this fd as ready.
|
||||
bool socket_ready_fd(int fd, bool loop_monitored);
|
||||
|
||||
@@ -555,7 +555,32 @@ void Application::after_loop_tasks_() {
|
||||
this->in_loop_ = false;
|
||||
}
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
bool Application::register_socket(struct lwip_sock *sock) {
|
||||
// It modifies monitored_sockets_ without locking — must only be called from the main loop.
|
||||
if (sock == nullptr)
|
||||
return false;
|
||||
esphome_lwip_hook_socket(sock);
|
||||
this->monitored_sockets_.push_back(sock);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::unregister_socket(struct lwip_sock *sock) {
|
||||
// It modifies monitored_sockets_ without locking — must only be called from the main loop.
|
||||
for (size_t i = 0; i < this->monitored_sockets_.size(); i++) {
|
||||
if (this->monitored_sockets_[i] != sock)
|
||||
continue;
|
||||
|
||||
// Swap with last element and pop - O(1) removal since order doesn't matter.
|
||||
// No need to unhook the netconn callback — all LwIP sockets share the same
|
||||
// static event_callback, and the socket will be closed by the caller.
|
||||
if (i < this->monitored_sockets_.size() - 1)
|
||||
this->monitored_sockets_[i] = this->monitored_sockets_.back();
|
||||
this->monitored_sockets_.pop_back();
|
||||
return;
|
||||
}
|
||||
}
|
||||
#elif defined(USE_SOCKET_SELECT_SUPPORT)
|
||||
bool Application::register_socket_fd(int fd) {
|
||||
// WARNING: This function is NOT thread-safe and must only be called from the main loop
|
||||
// It modifies socket_fds_ and related variables without locking
|
||||
@@ -574,15 +599,10 @@ bool Application::register_socket_fd(int fd) {
|
||||
#endif
|
||||
|
||||
this->socket_fds_.push_back(fd);
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
// Hook the socket's netconn callback for instant wake on receive events
|
||||
esphome_lwip_hook_socket(fd);
|
||||
#else
|
||||
this->socket_fds_changed_ = true;
|
||||
if (fd > this->max_fd_) {
|
||||
this->max_fd_ = fd;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -598,13 +618,9 @@ void Application::unregister_socket_fd(int fd) {
|
||||
continue;
|
||||
|
||||
// Swap with last element and pop - O(1) removal since order doesn't matter.
|
||||
// No need to unhook the netconn callback on fast select platforms — all LwIP
|
||||
// sockets share the same static event_callback, and the socket will be closed
|
||||
// by the caller.
|
||||
if (i < this->socket_fds_.size() - 1)
|
||||
this->socket_fds_[i] = this->socket_fds_.back();
|
||||
this->socket_fds_.pop_back();
|
||||
#ifndef USE_LWIP_FAST_SELECT
|
||||
this->socket_fds_changed_ = true;
|
||||
// Only recalculate max_fd if we removed the current max
|
||||
if (fd == this->max_fd_) {
|
||||
@@ -614,7 +630,6 @@ void Application::unregister_socket_fd(int fd) {
|
||||
this->max_fd_ = sock_fd;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -624,7 +639,7 @@ void Application::unregister_socket_fd(int fd) {
|
||||
void Application::yield_with_select_(uint32_t delay_ms) {
|
||||
// Delay while monitoring sockets. When delay_ms is 0, always yield() to ensure other tasks run.
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_LWIP_FAST_SELECT)
|
||||
// Fast path (ESP32/LibreTiny): reads rcvevent directly via lwip_socket_dbg_get_socket().
|
||||
// Fast path (ESP32/LibreTiny): reads rcvevent directly from cached lwip_sock pointers.
|
||||
// Safe because this runs on the main loop which owns socket lifetime (create, read, close).
|
||||
if (delay_ms == 0) [[unlikely]] {
|
||||
yield();
|
||||
@@ -635,8 +650,8 @@ void Application::yield_with_select_(uint32_t delay_ms) {
|
||||
// If a socket still has unread data (rcvevent > 0) but the task notification was already
|
||||
// consumed, ulTaskNotifyTake would block until timeout — adding up to delay_ms latency.
|
||||
// This scan preserves select() semantics: return immediately when any fd is ready.
|
||||
for (int fd : this->socket_fds_) {
|
||||
if (esphome_lwip_socket_has_data(fd)) {
|
||||
for (struct lwip_sock *sock : this->monitored_sockets_) {
|
||||
if (esphome_lwip_socket_has_data(sock)) {
|
||||
yield();
|
||||
return;
|
||||
}
|
||||
|
||||
+16
-16
@@ -487,14 +487,20 @@ class Application {
|
||||
|
||||
Scheduler scheduler;
|
||||
|
||||
/// Register/unregister a socket file descriptor to be monitored for read events.
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
/// These functions update the fd_set used by select() in the main loop.
|
||||
/// Register/unregister a socket to be monitored for read events.
|
||||
/// WARNING: These functions are NOT thread-safe. They must only be called from the main loop.
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
/// Fast select path: hooks netconn callback and registers for monitoring.
|
||||
/// @return true if registration was successful, false if sock is null
|
||||
bool register_socket(struct lwip_sock *sock);
|
||||
void unregister_socket(struct lwip_sock *sock);
|
||||
#elif defined(USE_SOCKET_SELECT_SUPPORT)
|
||||
/// Fallback select() path: monitors file descriptors.
|
||||
/// NOTE: File descriptors >= FD_SETSIZE (typically 10 on ESP) will be rejected with an error.
|
||||
/// @return true if registration was successful, false if fd exceeds limits
|
||||
bool register_socket_fd(int fd);
|
||||
void unregister_socket_fd(int fd);
|
||||
#endif
|
||||
|
||||
#ifdef USE_WAKE_LOOP_THREADSAFE
|
||||
/// Wake the main event loop from another FreeRTOS task.
|
||||
@@ -512,28 +518,18 @@ class Application {
|
||||
static void IRAM_ATTR wake_loop_isrsafe(int *px_higher_priority_task_woken) {
|
||||
esphome_lwip_wake_main_loop_from_isr(px_higher_priority_task_woken);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
protected:
|
||||
friend Component;
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && !defined(USE_LWIP_FAST_SELECT)
|
||||
friend bool socket::socket_ready_fd(int fd, bool loop_monitored);
|
||||
#endif
|
||||
friend void ::setup();
|
||||
friend void ::original_setup();
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
/// Fast path for Socket::ready() via friendship - skips negative fd check.
|
||||
/// Main loop only — with USE_LWIP_FAST_SELECT, reads rcvevent via
|
||||
/// lwip_socket_dbg_get_socket(), which has no refcount; safe only because
|
||||
/// the main loop owns socket lifetime (creates, reads, and closes sockets
|
||||
/// on the same thread).
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
bool is_socket_ready_(int fd) const { return esphome_lwip_socket_has_data(fd); }
|
||||
#else
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && !defined(USE_LWIP_FAST_SELECT)
|
||||
bool is_socket_ready_(int fd) const { return FD_ISSET(fd, &this->read_fds_); }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void register_component_(Component *comp);
|
||||
@@ -589,8 +585,12 @@ class Application {
|
||||
// and active_end_ is incremented
|
||||
// - This eliminates branch mispredictions from flag checking in the hot loop
|
||||
FixedVector<Component *> looping_components_{};
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
std::vector<struct lwip_sock *> monitored_sockets_; // Cached lwip_sock pointers for direct rcvevent read
|
||||
#elif defined(USE_SOCKET_SELECT_SUPPORT)
|
||||
std::vector<int> socket_fds_; // Vector of all monitored socket file descriptors
|
||||
#endif
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
#if defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_LWIP_FAST_SELECT)
|
||||
int wake_socket_fd_{-1}; // Shared wake notification socket for waking main loop from tasks
|
||||
#endif
|
||||
|
||||
@@ -151,6 +151,10 @@ _Static_assert(offsetof(struct netconn, callback) % sizeof(netconn_callback) ==
|
||||
_Static_assert(offsetof(struct lwip_sock, rcvevent) % sizeof(((struct lwip_sock *) 0)->rcvevent) == 0,
|
||||
"lwip_sock.rcvevent must be naturally aligned for atomic access");
|
||||
|
||||
// Verify the hardcoded offset used in the header's inline esphome_lwip_socket_has_data().
|
||||
_Static_assert(offsetof(struct lwip_sock, rcvevent) == ESPHOME_LWIP_SOCK_RCVEVENT_OFFSET,
|
||||
"lwip_sock.rcvevent offset changed — update ESPHOME_LWIP_SOCK_RCVEVENT_OFFSET in lwip_fast_select.h");
|
||||
|
||||
// Task handle for the main loop — written once in init(), read from TCP/IP and background tasks.
|
||||
static TaskHandle_t s_main_loop_task = NULL;
|
||||
|
||||
@@ -195,23 +199,11 @@ static inline struct lwip_sock *get_sock(int fd) {
|
||||
return sock;
|
||||
}
|
||||
|
||||
bool esphome_lwip_socket_has_data(int fd) {
|
||||
struct lwip_sock *sock = get_sock(fd);
|
||||
if (sock == NULL)
|
||||
return false;
|
||||
// volatile prevents the compiler from caching/reordering this cross-thread read.
|
||||
// The write side (TCP/IP thread) commits via SYS_ARCH_UNPROTECT which releases a
|
||||
// FreeRTOS mutex (ESP32) or resumes the scheduler (LibreTiny), ensuring the value
|
||||
// is visible. Aligned 16-bit reads are single-instruction loads (L16SI/LH/LDRH) on
|
||||
// Xtensa/RISC-V/ARM and cannot produce torn values.
|
||||
return *(volatile s16_t *) &sock->rcvevent > 0;
|
||||
struct lwip_sock *esphome_lwip_get_sock(int fd) {
|
||||
return get_sock(fd);
|
||||
}
|
||||
|
||||
void esphome_lwip_hook_socket(int fd) {
|
||||
struct lwip_sock *sock = get_sock(fd);
|
||||
if (sock == NULL)
|
||||
return;
|
||||
|
||||
void esphome_lwip_hook_socket(struct lwip_sock *sock) {
|
||||
// Save original callback once — all LwIP sockets share the same static event_callback
|
||||
// (DEFAULT_SOCKET_EVENTCB in sockets.c, used for SOCK_RAW, SOCK_DGRAM, and SOCK_STREAM).
|
||||
if (s_original_callback == NULL) {
|
||||
|
||||
@@ -4,6 +4,16 @@
|
||||
// Replaces lwip_select() with direct rcvevent reads and FreeRTOS task notifications.
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Forward declare lwip_sock for C++ callers that store cached pointers.
|
||||
// The full definition is only available in the .c file (lwip/priv/sockets_priv.h
|
||||
// conflicts with C++ compilation units).
|
||||
struct lwip_sock;
|
||||
|
||||
// Byte offset of rcvevent (s16_t) within struct lwip_sock.
|
||||
// Verified at compile time in lwip_fast_select.c via _Static_assert.
|
||||
#define ESPHOME_LWIP_SOCK_RCVEVENT_OFFSET 8
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -13,16 +23,31 @@ extern "C" {
|
||||
/// Saves the current task handle for xTaskNotifyGive() wake notifications.
|
||||
void esphome_lwip_fast_select_init(void);
|
||||
|
||||
/// Check if a LwIP socket has data ready via direct rcvevent read (~215 ns per socket).
|
||||
/// Uses lwip_socket_dbg_get_socket() — a direct array lookup without the refcount that
|
||||
/// get_socket()/done_socket() uses. Safe because the caller owns the socket lifetime:
|
||||
/// both has_data reads and socket close/unregister happen on the main loop thread.
|
||||
bool esphome_lwip_socket_has_data(int fd);
|
||||
/// Look up a LwIP socket struct from a file descriptor.
|
||||
/// Returns NULL if fd is invalid or the socket/netconn is not initialized.
|
||||
/// Use this at registration time to cache the pointer for esphome_lwip_socket_has_data().
|
||||
struct lwip_sock *esphome_lwip_get_sock(int fd);
|
||||
|
||||
/// Check if a cached LwIP socket has data ready via direct rcvevent read.
|
||||
/// The sock pointer must have been obtained from esphome_lwip_get_sock() and must
|
||||
/// remain valid (caller owns socket lifetime — no concurrent close).
|
||||
/// Hot path: inlined volatile 16-bit load — no function call overhead.
|
||||
/// Uses offset-based access because lwip/priv/sockets_priv.h conflicts with C++.
|
||||
/// The offset is verified at compile time in lwip_fast_select.c.
|
||||
static inline bool esphome_lwip_socket_has_data(struct lwip_sock *sock) {
|
||||
// volatile prevents the compiler from caching/reordering this cross-thread read.
|
||||
// The write side (TCP/IP thread) commits via SYS_ARCH_UNPROTECT which releases a
|
||||
// FreeRTOS mutex (ESP32) or resumes the scheduler (LibreTiny), ensuring the value
|
||||
// is visible. Aligned 16-bit reads are single-instruction loads (L16SI/LH/LDRH) on
|
||||
// Xtensa/RISC-V/ARM and cannot produce torn values.
|
||||
return *(volatile int16_t *) ((char *) sock + ESPHOME_LWIP_SOCK_RCVEVENT_OFFSET) > 0;
|
||||
}
|
||||
|
||||
/// Hook a socket's netconn callback to notify the main loop task on receive events.
|
||||
/// Wraps the original event_callback with one that also calls xTaskNotifyGive().
|
||||
/// Must be called from the main loop after socket creation.
|
||||
void esphome_lwip_hook_socket(int fd);
|
||||
/// 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.
|
||||
|
||||
Reference in New Issue
Block a user