ESPHome 2026.10.0-dev
Loading...
Searching...
No Matches
helpers.cpp
Go to the documentation of this file.
3
4#ifdef USE_ESP32
5
6#include "esp_efuse.h"
7#include "esp_efuse_table.h"
8#include "esp_mac.h"
9
10#include <freertos/FreeRTOS.h>
11#include <freertos/portmacro.h>
12#include "esphome/core/log.h"
13#include "esp_random.h"
14#include "esp_system.h"
15
16namespace esphome {
17
18static const char *const TAG = "esp32";
19
20bool random_bytes(uint8_t *data, size_t len) {
21 esp_fill_random(data, len);
22 return true;
23}
24
25// only affects the executing core
26// so should not be used as a mutex lock, only to get accurate timing
27IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
28IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
29
30#ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING
31#include "lwip/priv/tcpip_priv.h"
32#endif
33
35#ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING
36 // When CONFIG_LWIP_TCPIP_CORE_LOCKING is enabled, lwIP uses a global mutex to protect
37 // its internal state. Any thread can take this lock to safely access lwIP APIs.
38 //
39 // sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER) returns true if the current thread
40 // already holds the lwIP core lock. This prevents recursive locking attempts and
41 // allows nested LwIPLock instances to work correctly.
42 //
43 // If we don't already hold the lock, acquire it. This will block until the lock
44 // is available if another thread currently holds it.
45 if (!sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) {
46 LOCK_TCPIP_CORE();
47 }
48#endif
49}
50
52#ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING
53 // Only release the lwIP core lock if this thread currently holds it.
54 //
55 // sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER) queries lwIP's internal lock
56 // ownership tracking. It returns true only if the current thread is registered
57 // as the lock holder.
58 //
59 // This check is essential because:
60 // 1. We may not have acquired the lock in the constructor (if we already held it)
61 // 2. The lock might have been released by other means between constructor and destructor
62 // 3. Calling UNLOCK_TCPIP_CORE() without holding the lock causes undefined behavior
63 if (sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) {
64 UNLOCK_TCPIP_CORE();
65 }
66#endif
67}
68
70static bool read_valid_mac(uint8_t *mac, esp_err_t err) { return err == ESP_OK && mac_address_is_valid(mac); }
71
72static constexpr size_t MAC_ADDRESS_SIZE_BITS = MAC_ADDRESS_SIZE * 8; // 48 bits
73
74// Must not use the ESPHome logger (may run before it exists, e.g. from app_main()).
75bool get_custom_mac_address(uint8_t *mac) {
76 // has_custom_mac_address() checks the raw eFuse field, while the reads below select their
77 // method differently and may still fail (CRC), so the result must be validated again.
79 return false;
80#if defined(CONFIG_SOC_IEEE802154_SUPPORTED)
81 return read_valid_mac(mac, esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, MAC_ADDRESS_SIZE_BITS));
82#else
83 return read_valid_mac(mac, esp_efuse_mac_get_custom(mac));
84#endif
85}
86
87void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
88 if (get_custom_mac_address(mac)) {
89 return;
90 }
91#if defined(CONFIG_SOC_IEEE802154_SUPPORTED)
92 // When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default
93 // returns the 802.15.4 EUI-64 address, so we read directly from eFuse instead.
94 // This already reads raw eFuse bytes, so there is no CRC-bypass fallback
95 // (unlike the non-IEEE802154 path where esp_efuse_mac_get_default does CRC checks).
96 if (read_valid_mac(mac, esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, MAC_ADDRESS_SIZE_BITS))) {
97 return;
98 }
99#else
100 if (read_valid_mac(mac, esp_efuse_mac_get_default(mac))) {
101 return;
102 }
103 // Default MAC read failed (e.g., eFuse CRC error) - try reading raw eFuse bytes
104 // directly, bypassing CRC validation. A MAC that passes mac_address_is_valid()
105 // (non-zero, non-broadcast, unicast) is almost certainly the real factory MAC
106 // with a corrupted CRC byte, which is far better than returning garbage or zeros.
107 if (read_valid_mac(mac, esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, MAC_ADDRESS_SIZE_BITS))) {
108 ESP_LOGW(TAG, "eFuse MAC CRC failed but raw bytes appear valid - using raw eFuse MAC");
109 return;
110 }
111#endif
112 // All methods failed - zero the MAC rather than returning garbage
113 ESP_LOGE(TAG, "Failed to read a valid MAC address from eFuse");
114 memset(mac, 0, MAC_ADDRESS_SIZE);
115}
116
117void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); }
118
120#if !defined(USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC)
121 uint8_t mac[MAC_ADDRESS_SIZE];
122 // do not use 'esp_efuse_mac_get_custom(mac)' because it drops an error in the logs whenever it fails
123#ifndef USE_ESP32_VARIANT_ESP32
124 return (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA_MAC_CUSTOM, mac, MAC_ADDRESS_SIZE_BITS) == ESP_OK) &&
126#else
127 return (esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, MAC_ADDRESS_SIZE_BITS) == ESP_OK) &&
129#endif
130#else
131 return false;
132#endif
133}
134
135} // namespace esphome
136
137#endif // USE_ESP32
bool random_bytes(uint8_t *data, size_t len)
Generate len random bytes using the platform's secure RNG (hardware RNG or OS CSPRNG).
Definition helpers.cpp:20
bool mac_address_is_valid(const uint8_t *mac)
Check if the MAC address is not all zeros or all ones.
Definition helpers.cpp:843
const void size_t len
Definition hal.h:64
bool has_custom_mac_address()
Check if a custom MAC address is set (ESP32 & variants)
Definition helpers.cpp:119
void set_mac_address(uint8_t *mac)
Set the MAC address to use from the provided byte array (6 bytes).
Definition helpers.cpp:117
void get_mac_address_raw(uint8_t *mac)
Get the device MAC address as raw bytes, written into the provided byte array (6 bytes).
Definition helpers.cpp:87
bool get_custom_mac_address(uint8_t *mac)
Read the custom MAC address from eFuse into the provided byte array (6 bytes).
Definition helpers.cpp:75