#pragma once // Fast socket monitoring for ESP32 and LibreTiny (LwIP >= 2.1.3) // Replaces lwip_select() with direct rcvevent reads and FreeRTOS task notifications. #include #include // 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. // Anonymous enum for a compile-time constant that works in both C and C++. enum { ESPHOME_LWIP_SOCK_RCVEVENT_OFFSET = 8 }; #ifdef __cplusplus extern "C" { #endif /// 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 unlocked hint read of rcvevent. /// This avoids lwIP core lock contention between the main loop (CPU0) and /// streaming/networking work (CPU1). Correctness is preserved because callers /// already handle EWOULDBLOCK on nonblocking sockets — a stale hint simply causes /// a harmless retry on the next loop iteration. In practice, stale reads have not /// been observed across multi-day testing, but the design does not depend on that. /// /// 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 and size are verified at compile time in lwip_fast_select.c. static inline bool esphome_lwip_socket_has_data(struct lwip_sock *sock) { // Unlocked hint read — no lwIP core lock needed. // 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 + (int) 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. /// The sock pointer must have been obtained from esphome_lwip_get_sock(). void esphome_lwip_hook_socket(struct lwip_sock *sock); /// 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, /// hooks, refcounting) — just a direct pcb->flags bit set/clear. /// 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); #ifdef __cplusplus } #endif