mirror of
https://github.com/esphome/esphome.git
synced 2025-07-28 14:16:40 +00:00
Fix lwIP thread safety assertion failures on ESP32 (#9570)
This commit is contained in:
parent
46f5c44b37
commit
e255d73c29
@ -4,6 +4,7 @@
|
|||||||
#include "esphome/components/network/ip_address.h"
|
#include "esphome/components/network/ip_address.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/util.h"
|
#include "esphome/core/util.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
#include <lwip/igmp.h>
|
#include <lwip/igmp.h>
|
||||||
#include <lwip/init.h>
|
#include <lwip/init.h>
|
||||||
@ -71,7 +72,11 @@ bool E131Component::join_igmp_groups_() {
|
|||||||
ip4_addr_t multicast_addr =
|
ip4_addr_t multicast_addr =
|
||||||
network::IPAddress(239, 255, ((universe.first >> 8) & 0xff), ((universe.first >> 0) & 0xff));
|
network::IPAddress(239, 255, ((universe.first >> 8) & 0xff), ((universe.first >> 0) & 0xff));
|
||||||
|
|
||||||
auto err = igmp_joingroup(IP4_ADDR_ANY4, &multicast_addr);
|
err_t err;
|
||||||
|
{
|
||||||
|
LwIPLock lock;
|
||||||
|
err = igmp_joingroup(IP4_ADDR_ANY4, &multicast_addr);
|
||||||
|
}
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
ESP_LOGW(TAG, "IGMP join for %d universe of E1.31 failed. Multicast might not work.", universe.first);
|
ESP_LOGW(TAG, "IGMP join for %d universe of E1.31 failed. Multicast might not work.", universe.first);
|
||||||
@ -104,6 +109,7 @@ void E131Component::leave_(int universe) {
|
|||||||
if (listen_method_ == E131_MULTICAST) {
|
if (listen_method_ == E131_MULTICAST) {
|
||||||
ip4_addr_t multicast_addr = network::IPAddress(239, 255, ((universe >> 8) & 0xff), ((universe >> 0) & 0xff));
|
ip4_addr_t multicast_addr = network::IPAddress(239, 255, ((universe >> 8) & 0xff), ((universe >> 0) & 0xff));
|
||||||
|
|
||||||
|
LwIPLock lock;
|
||||||
igmp_leavegroup(IP4_ADDR_ANY4, &multicast_addr);
|
igmp_leavegroup(IP4_ADDR_ANY4, &multicast_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,45 @@ void Mutex::unlock() { xSemaphoreGive(this->handle_); }
|
|||||||
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
|
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
|
||||||
IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
|
IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
|
||||||
|
|
||||||
|
#ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING
|
||||||
|
#include "lwip/priv/tcpip_priv.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LwIPLock::LwIPLock() {
|
||||||
|
#ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING
|
||||||
|
// When CONFIG_LWIP_TCPIP_CORE_LOCKING is enabled, lwIP uses a global mutex to protect
|
||||||
|
// its internal state. Any thread can take this lock to safely access lwIP APIs.
|
||||||
|
//
|
||||||
|
// sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER) returns true if the current thread
|
||||||
|
// already holds the lwIP core lock. This prevents recursive locking attempts and
|
||||||
|
// allows nested LwIPLock instances to work correctly.
|
||||||
|
//
|
||||||
|
// If we don't already hold the lock, acquire it. This will block until the lock
|
||||||
|
// is available if another thread currently holds it.
|
||||||
|
if (!sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) {
|
||||||
|
LOCK_TCPIP_CORE();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
LwIPLock::~LwIPLock() {
|
||||||
|
#ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING
|
||||||
|
// Only release the lwIP core lock if this thread currently holds it.
|
||||||
|
//
|
||||||
|
// sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER) queries lwIP's internal lock
|
||||||
|
// ownership tracking. It returns true only if the current thread is registered
|
||||||
|
// as the lock holder.
|
||||||
|
//
|
||||||
|
// This check is essential because:
|
||||||
|
// 1. We may not have acquired the lock in the constructor (if we already held it)
|
||||||
|
// 2. The lock might have been released by other means between constructor and destructor
|
||||||
|
// 3. Calling UNLOCK_TCPIP_CORE() without holding the lock causes undefined behavior
|
||||||
|
if (sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) {
|
||||||
|
UNLOCK_TCPIP_CORE();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
|
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
|
||||||
#if defined(CONFIG_SOC_IEEE802154_SUPPORTED)
|
#if defined(CONFIG_SOC_IEEE802154_SUPPORTED)
|
||||||
// When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default
|
// When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default
|
||||||
|
@ -22,6 +22,10 @@ void Mutex::unlock() {}
|
|||||||
IRAM_ATTR InterruptLock::InterruptLock() { state_ = xt_rsil(15); }
|
IRAM_ATTR InterruptLock::InterruptLock() { state_ = xt_rsil(15); }
|
||||||
IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(state_); }
|
IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(state_); }
|
||||||
|
|
||||||
|
// ESP8266 doesn't support lwIP core locking, so this is a no-op
|
||||||
|
LwIPLock::LwIPLock() {}
|
||||||
|
LwIPLock::~LwIPLock() {}
|
||||||
|
|
||||||
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
|
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
|
||||||
wifi_get_macaddr(STATION_IF, mac);
|
wifi_get_macaddr(STATION_IF, mac);
|
||||||
}
|
}
|
||||||
|
@ -420,6 +420,7 @@ network::IPAddresses EthernetComponent::get_ip_addresses() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
network::IPAddress EthernetComponent::get_dns_address(uint8_t num) {
|
network::IPAddress EthernetComponent::get_dns_address(uint8_t num) {
|
||||||
|
LwIPLock lock;
|
||||||
const ip_addr_t *dns_ip = dns_getserver(num);
|
const ip_addr_t *dns_ip = dns_getserver(num);
|
||||||
return dns_ip;
|
return dns_ip;
|
||||||
}
|
}
|
||||||
@ -527,6 +528,7 @@ void EthernetComponent::start_connect_() {
|
|||||||
ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
|
ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
|
||||||
|
|
||||||
if (this->manual_ip_.has_value()) {
|
if (this->manual_ip_.has_value()) {
|
||||||
|
LwIPLock lock;
|
||||||
if (this->manual_ip_->dns1.is_set()) {
|
if (this->manual_ip_->dns1.is_set()) {
|
||||||
ip_addr_t d;
|
ip_addr_t d;
|
||||||
d = this->manual_ip_->dns1;
|
d = this->manual_ip_->dns1;
|
||||||
@ -559,8 +561,13 @@ bool EthernetComponent::is_connected() { return this->state_ == EthernetComponen
|
|||||||
void EthernetComponent::dump_connect_params_() {
|
void EthernetComponent::dump_connect_params_() {
|
||||||
esp_netif_ip_info_t ip;
|
esp_netif_ip_info_t ip;
|
||||||
esp_netif_get_ip_info(this->eth_netif_, &ip);
|
esp_netif_get_ip_info(this->eth_netif_, &ip);
|
||||||
const ip_addr_t *dns_ip1 = dns_getserver(0);
|
const ip_addr_t *dns_ip1;
|
||||||
const ip_addr_t *dns_ip2 = dns_getserver(1);
|
const ip_addr_t *dns_ip2;
|
||||||
|
{
|
||||||
|
LwIPLock lock;
|
||||||
|
dns_ip1 = dns_getserver(0);
|
||||||
|
dns_ip2 = dns_getserver(1);
|
||||||
|
}
|
||||||
|
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
" IP Address: %s\n"
|
" IP Address: %s\n"
|
||||||
|
@ -26,6 +26,10 @@ void Mutex::unlock() { xSemaphoreGive(this->handle_); }
|
|||||||
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
|
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
|
||||||
IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
|
IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
|
||||||
|
|
||||||
|
// LibreTiny doesn't support lwIP core locking, so this is a no-op
|
||||||
|
LwIPLock::LwIPLock() {}
|
||||||
|
LwIPLock::~LwIPLock() {}
|
||||||
|
|
||||||
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
|
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
|
||||||
WiFi.macAddress(mac);
|
WiFi.macAddress(mac);
|
||||||
}
|
}
|
||||||
|
@ -193,13 +193,17 @@ void MQTTClientComponent::start_dnslookup_() {
|
|||||||
this->dns_resolve_error_ = false;
|
this->dns_resolve_error_ = false;
|
||||||
this->dns_resolved_ = false;
|
this->dns_resolved_ = false;
|
||||||
ip_addr_t addr;
|
ip_addr_t addr;
|
||||||
|
err_t err;
|
||||||
|
{
|
||||||
|
LwIPLock lock;
|
||||||
#if USE_NETWORK_IPV6
|
#if USE_NETWORK_IPV6
|
||||||
err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr,
|
err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, MQTTClientComponent::dns_found_callback,
|
||||||
MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV6_IPV4);
|
this, LWIP_DNS_ADDRTYPE_IPV6_IPV4);
|
||||||
#else
|
#else
|
||||||
err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr,
|
err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, MQTTClientComponent::dns_found_callback,
|
||||||
MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV4);
|
this, LWIP_DNS_ADDRTYPE_IPV4);
|
||||||
#endif /* USE_NETWORK_IPV6 */
|
#endif /* USE_NETWORK_IPV6 */
|
||||||
|
}
|
||||||
switch (err) {
|
switch (err) {
|
||||||
case ERR_OK: {
|
case ERR_OK: {
|
||||||
// Got IP immediately
|
// Got IP immediately
|
||||||
|
@ -44,6 +44,10 @@ void Mutex::unlock() {}
|
|||||||
IRAM_ATTR InterruptLock::InterruptLock() { state_ = save_and_disable_interrupts(); }
|
IRAM_ATTR InterruptLock::InterruptLock() { state_ = save_and_disable_interrupts(); }
|
||||||
IRAM_ATTR InterruptLock::~InterruptLock() { restore_interrupts(state_); }
|
IRAM_ATTR InterruptLock::~InterruptLock() { restore_interrupts(state_); }
|
||||||
|
|
||||||
|
// RP2040 doesn't support lwIP core locking, so this is a no-op
|
||||||
|
LwIPLock::LwIPLock() {}
|
||||||
|
LwIPLock::~LwIPLock() {}
|
||||||
|
|
||||||
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
|
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
|
||||||
#ifdef USE_WIFI
|
#ifdef USE_WIFI
|
||||||
WiFi.macAddress(mac);
|
WiFi.macAddress(mac);
|
||||||
|
@ -20,10 +20,6 @@
|
|||||||
#include "lwip/dns.h"
|
#include "lwip/dns.h"
|
||||||
#include "lwip/err.h"
|
#include "lwip/err.h"
|
||||||
|
|
||||||
#ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING
|
|
||||||
#include "lwip/priv/tcpip_priv.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
@ -295,25 +291,16 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!manual_ip.has_value()) {
|
if (!manual_ip.has_value()) {
|
||||||
// sntp_servermode_dhcp lwip/sntp.c (Required to lock TCPIP core functionality!)
|
// sntp_servermode_dhcp lwip/sntp.c (Required to lock TCPIP core functionality!)
|
||||||
// https://github.com/esphome/issues/issues/6591
|
// https://github.com/esphome/issues/issues/6591
|
||||||
// https://github.com/espressif/arduino-esp32/issues/10526
|
// https://github.com/espressif/arduino-esp32/issues/10526
|
||||||
#ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING
|
{
|
||||||
if (!sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) {
|
LwIPLock lock;
|
||||||
LOCK_TCPIP_CORE();
|
// lwIP starts the SNTP client if it gets an SNTP server from DHCP. We don't need the time, and more importantly,
|
||||||
|
// the built-in SNTP client has a memory leak in certain situations. Disable this feature.
|
||||||
|
// https://github.com/esphome/issues/issues/2299
|
||||||
|
sntp_servermode_dhcp(false);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
// lwIP starts the SNTP client if it gets an SNTP server from DHCP. We don't need the time, and more importantly,
|
|
||||||
// the built-in SNTP client has a memory leak in certain situations. Disable this feature.
|
|
||||||
// https://github.com/esphome/issues/issues/2299
|
|
||||||
sntp_servermode_dhcp(false);
|
|
||||||
|
|
||||||
#ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING
|
|
||||||
if (sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) {
|
|
||||||
UNLOCK_TCPIP_CORE();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// No manual IP is set; use DHCP client
|
// No manual IP is set; use DHCP client
|
||||||
if (dhcp_status != ESP_NETIF_DHCP_STARTED) {
|
if (dhcp_status != ESP_NETIF_DHCP_STARTED) {
|
||||||
|
@ -683,6 +683,23 @@ class InterruptLock {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Helper class to lock the lwIP TCPIP core when making lwIP API calls from non-TCPIP threads.
|
||||||
|
*
|
||||||
|
* This is needed on multi-threaded platforms (ESP32) when CONFIG_LWIP_TCPIP_CORE_LOCKING is enabled.
|
||||||
|
* It ensures thread-safe access to lwIP APIs.
|
||||||
|
*
|
||||||
|
* @note This follows the same pattern as InterruptLock - platform-specific implementations in helpers.cpp
|
||||||
|
*/
|
||||||
|
class LwIPLock {
|
||||||
|
public:
|
||||||
|
LwIPLock();
|
||||||
|
~LwIPLock();
|
||||||
|
|
||||||
|
// Delete copy constructor and copy assignment operator to prevent accidental copying
|
||||||
|
LwIPLock(const LwIPLock &) = delete;
|
||||||
|
LwIPLock &operator=(const LwIPLock &) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
/** Helper class to request `loop()` to be called as fast as possible.
|
/** Helper class to request `loop()` to be called as fast as possible.
|
||||||
*
|
*
|
||||||
* Usually the ESPHome main loop runs at 60 Hz, sleeping in between invocations of `loop()` if necessary. When a higher
|
* Usually the ESPHome main loop runs at 60 Hz, sleeping in between invocations of `loop()` if necessary. When a higher
|
||||||
|
Loading…
x
Reference in New Issue
Block a user