This commit is contained in:
J. Nick Koston
2026-02-23 21:17:50 -06:00
parent c9c73ec6e9
commit 3fd24b779c
+13 -10
View File
@@ -11,7 +11,8 @@
// ====================== // ======================
// Three threads interact with this code: // Three threads interact with this code:
// 1. Main loop task — calls init, has_data, hook // 1. Main loop task — calls init, has_data, hook
// 2. LwIP TCP/IP task — calls event_callback (which reads s_original_callback, writes rcvevent) // 2. LwIP TCP/IP task — calls event_callback (reads s_original_callback; writes rcvevent
// via the original callback under SYS_ARCH_PROTECT/UNPROTECT mutex)
// 3. Background tasks — call wake_main_loop // 3. Background tasks — call wake_main_loop
// //
// Shared state and safety rationale: // Shared state and safety rationale:
@@ -40,12 +41,14 @@
// (the wrapper itself calls the original), so either value is correct. // (the wrapper itself calls the original), so either value is correct.
// //
// sock->rcvevent (s16_t, 2 bytes): // sock->rcvevent (s16_t, 2 bytes):
// Written by TCP/IP thread in event_callback (via SYS_ARCH_INC/DEC under lock). // Written by TCP/IP thread in event_callback under SYS_ARCH_PROTECT/UNPROTECT.
// Read by main loop in has_data() via volatile cast. // Read by main loop in has_data() via volatile cast.
// Safe: aligned 16-bit reads are atomic on Xtensa/RISC-V. The write side commits // Safe: SYS_ARCH_UNPROTECT releases a FreeRTOS mutex (sys_mutex_unlock), which
// via SYS_ARCH_UNPROTECT (portEXIT_CRITICAL) which flushes the write buffer. // internally uses a critical section with memory barrier (rsync on Xtensa),
// ESP32 internal SRAM has no per-core data cache, so the volatile load always // ensuring the write is committed before the mutex is released. The volatile
// reads the committed value. volatile prevents compiler from caching the read. // cast prevents the compiler from caching the read. Aligned 16-bit reads are
// single-instruction loads on Xtensa (L16SI) and RISC-V (LH), which cannot
// produce torn values.
// //
// FreeRTOS task notification value: // FreeRTOS task notification value:
// Written by TCP/IP thread (xTaskNotifyGive in callback) and background tasks // Written by TCP/IP thread (xTaskNotifyGive in callback) and background tasks
@@ -115,10 +118,10 @@ bool esphome_lwip_socket_has_data(int fd) {
if (sock == NULL || sock->conn == NULL) if (sock == NULL || sock->conn == NULL)
return false; return false;
// volatile prevents the compiler from caching/reordering this cross-thread read. // volatile prevents the compiler from caching/reordering this cross-thread read.
// The write side (TCP/IP thread) commits via SYS_ARCH_UNPROTECT (portEXIT_CRITICAL), // The write side (TCP/IP thread) commits via SYS_ARCH_UNPROTECT which releases a
// which flushes the write buffer. ESP32 internal SRAM has no per-core data cache, // FreeRTOS mutex with a memory barrier (rsync on Xtensa), ensuring the value is
// so the volatile load always reads the committed value from SRAM. // visible. Aligned 16-bit reads are single-instruction loads (L16SI/LH) on
// Aligned 16-bit reads are naturally atomic on Xtensa/RISC-V. // Xtensa/RISC-V and cannot produce torn values.
return *(volatile s16_t *) &sock->rcvevent > 0; return *(volatile s16_t *) &sock->rcvevent > 0;
} }