From 5be4f746ba999cba55a8389ac8ef126c9f5f83cb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Apr 2026 05:46:56 -0500 Subject: [PATCH] [esp32] Move HAL bodies from components/esp32/core.cpp into core/hal/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-platform follow-up to #15977. Move out-of-line HAL bodies (millis(), arch_restart(), arch_get_cpu_freq_hz()) from components/esp32/core.cpp into a new esphome/core/hal/hal_esp32.cpp so HAL implementation finally lives next to the HAL header. Inline the trivial one-liners directly in hal_esp32.h: delayMicroseconds(us) -> delay_microseconds_safe(us) arch_feed_wdt() -> esp_task_wdt_reset() arch_get_cpu_cycle_count() -> esp_cpu_get_cycle_count() arch_init() stays in components/esp32/core.cpp because it uses esp32::crash_handler_read_and_clear() from the component-private crash_handler.h header — moving it would require an awkward layering inversion of core/ -> components/esp32/. config.py FILTER_SOURCE_FILES gains a hal/hal_esp32.cpp entry so only ESP32 builds compile it (same pattern wake/wake_*.cpp uses). Cross-platform decls of delayMicroseconds(), arch_feed_wdt(), and arch_get_cpu_cycle_count() are dropped from the dispatcher hal.h and moved into each per-platform header — that way subsequent per-platform follow-up PRs (libretiny, rp2040, host, zephyr) only need to touch their own platform header instead of accumulating gates in hal.h. --- esphome/components/esp32/core.cpp | 37 ++------------------------ esphome/core/config.py | 10 +++++++ esphome/core/hal.h | 10 +++---- esphome/core/hal/hal_esp32.cpp | 44 +++++++++++++++++++++++++++++++ esphome/core/hal/hal_esp32.h | 12 +++++++++ esphome/core/hal/hal_host.h | 4 +++ esphome/core/hal/hal_libretiny.h | 4 +++ esphome/core/hal/hal_rp2040.h | 4 +++ esphome/core/hal/hal_zephyr.h | 4 +++ 9 files changed, 87 insertions(+), 42 deletions(-) create mode 100644 esphome/core/hal/hal_esp32.cpp diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 4886745c068..267052ea2e3 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -6,12 +6,8 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "preferences.h" -#include -#include -#include #include #include -#include #include #include @@ -23,29 +19,8 @@ extern "C" __attribute__((weak)) void initArduino() {} namespace esphome { // yield(), delay(), micros(), millis_64() inlined in hal.h. -// Use xTaskGetTickCount() when tick rate is 1 kHz (ESPHome's default via sdkconfig), -// falling back to esp_timer for non-standard rates. IRAM_ATTR is required because -// Wiegand and ZyAura call millis() from IRAM_ATTR ISR handlers on ESP32. -// xTaskGetTickCountFromISR() is used in ISR context to satisfy the FreeRTOS API contract. -uint32_t IRAM_ATTR HOT millis() { -#if CONFIG_FREERTOS_HZ == 1000 - if (xPortInIsrContext()) [[unlikely]] { - return xTaskGetTickCountFromISR(); - } - return xTaskGetTickCount(); -#else - return micros_to_millis(static_cast(esp_timer_get_time())); -#endif -} -void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); } -void arch_restart() { - esp_restart(); - // restart() doesn't always end execution - while (true) { // NOLINT(clang-diagnostic-unreachable-code) - yield(); - } -} - +// millis(), arch_restart(), arch_get_cpu_freq_hz() out-of-line in hal/hal_esp32.cpp. +// delayMicroseconds(), arch_feed_wdt(), arch_get_cpu_cycle_count() inlined in hal/hal_esp32.h. void arch_init() { #ifdef USE_ESP32_CRASH_HANDLER // Read crash data from previous boot before anything else @@ -61,14 +36,6 @@ void arch_init() { esp_ota_mark_app_valid_cancel_rollback(); #endif } -void HOT arch_feed_wdt() { esp_task_wdt_reset(); } - -uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); } -uint32_t arch_get_cpu_freq_hz() { - uint32_t freq = 0; - esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq); - return freq; -} TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static StaticTask_t loop_task_tcb; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/core/config.py b/esphome/core/config.py index b4e81ce49fa..ae2a40d0363 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -815,6 +815,16 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( "wake/wake_zephyr.cpp": { PlatformFramework.NRF52_ZEPHYR, }, + # Per-platform HAL out-of-line implementations — hal.h dispatches to + # exactly one platform header based on USE_*, and the matching .cpp + # provides the few HAL functions that stay out-of-line. Other + # platforms still keep their out-of-line bodies in + # components//core.cpp until their per-platform follow-up + # PR moves them here. + "hal/hal_esp32.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, # Note: lock_free_queue.h and event_pool.h are header files and don't need to be filtered # as they are only included when needed by the preprocessor } diff --git a/esphome/core/hal.h b/esphome/core/hal.h index e20797cf95d..312effa7b0e 100644 --- a/esphome/core/hal.h +++ b/esphome/core/hal.h @@ -30,15 +30,11 @@ namespace esphome { -// ESP8266 inlines delayMicroseconds() and arch_feed_wdt() in hal/hal_esp8266.h; -// every other platform keeps them out-of-line in components//core.cpp. -#ifndef USE_ESP8266 -void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming) -void arch_feed_wdt(); -#endif +// Cross-platform declarations. delayMicroseconds(), arch_feed_wdt(), +// arch_get_cpu_cycle_count() vary per platform (some inline, some +// out-of-line) so they live in hal/hal_.h. void __attribute__((noreturn)) arch_restart(); void arch_init(); -uint32_t arch_get_cpu_cycle_count(); uint32_t arch_get_cpu_freq_hz(); #ifndef USE_ESP8266 diff --git a/esphome/core/hal/hal_esp32.cpp b/esphome/core/hal/hal_esp32.cpp new file mode 100644 index 00000000000..a881953208c --- /dev/null +++ b/esphome/core/hal/hal_esp32.cpp @@ -0,0 +1,44 @@ +#ifdef USE_ESP32 + +#include "esphome/core/hal.h" + +#include +#include +#include +#include +#include + +namespace esphome { + +// Use xTaskGetTickCount() when tick rate is 1 kHz (ESPHome's default via sdkconfig), +// falling back to esp_timer for non-standard rates. IRAM_ATTR is required because +// Wiegand and ZyAura call millis() from IRAM_ATTR ISR handlers on ESP32. +// xTaskGetTickCountFromISR() is used in ISR context to satisfy the FreeRTOS API contract. +uint32_t IRAM_ATTR HOT millis() { +#if CONFIG_FREERTOS_HZ == 1000 + if (xPortInIsrContext()) [[unlikely]] { + return xTaskGetTickCountFromISR(); + } + return xTaskGetTickCount(); +#else + return micros_to_millis(static_cast(esp_timer_get_time())); +#endif +} + +void arch_restart() { + esp_restart(); + // restart() doesn't always end execution + while (true) { // NOLINT(clang-diagnostic-unreachable-code) + yield(); + } +} + +uint32_t arch_get_cpu_freq_hz() { + uint32_t freq = 0; + esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq); + return freq; +} + +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/core/hal/hal_esp32.h b/esphome/core/hal/hal_esp32.h index e755337540d..2bff4244419 100644 --- a/esphome/core/hal/hal_esp32.h +++ b/esphome/core/hal/hal_esp32.h @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include @@ -15,6 +17,11 @@ namespace esphome { +// Forward decl from helpers.h (esphome/core/helpers.h) — kept here so this +// header does not need to pull the rest of helpers.h. +// NOLINTNEXTLINE(readability-redundant-declaration) +void delay_microseconds_safe(uint32_t us); + /// Returns true when executing inside an interrupt handler. __attribute__((always_inline)) inline bool in_isr_context() { return xPortInIsrContext() != 0; } @@ -30,6 +37,11 @@ __attribute__((always_inline)) inline uint64_t millis_64() { return micros_to_millis(static_cast(esp_timer_get_time())); } +// NOLINTNEXTLINE(readability-identifier-naming) +__attribute__((always_inline)) inline void delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); } +__attribute__((always_inline)) inline void arch_feed_wdt() { esp_task_wdt_reset(); } +__attribute__((always_inline)) inline uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); } + } // namespace esphome #endif // USE_ESP32 diff --git a/esphome/core/hal/hal_host.h b/esphome/core/hal/hal_host.h index 145fe4ea9c1..682a1a422b4 100644 --- a/esphome/core/hal/hal_host.h +++ b/esphome/core/hal/hal_host.h @@ -19,6 +19,10 @@ uint32_t micros(); uint32_t millis(); uint64_t millis_64(); +void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming) +void arch_feed_wdt(); +uint32_t arch_get_cpu_cycle_count(); + } // namespace esphome #endif // USE_HOST diff --git a/esphome/core/hal/hal_libretiny.h b/esphome/core/hal/hal_libretiny.h index e0d92735bbb..e9d33b7753b 100644 --- a/esphome/core/hal/hal_libretiny.h +++ b/esphome/core/hal/hal_libretiny.h @@ -88,6 +88,10 @@ __attribute__((always_inline)) inline uint32_t millis() { return static_cast(::time_us_64()); } +void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming) +void arch_feed_wdt(); +uint32_t arch_get_cpu_cycle_count(); + } // namespace esphome #endif // USE_RP2040 diff --git a/esphome/core/hal/hal_zephyr.h b/esphome/core/hal/hal_zephyr.h index e28be5c775d..6707c85b2c2 100644 --- a/esphome/core/hal/hal_zephyr.h +++ b/esphome/core/hal/hal_zephyr.h @@ -19,6 +19,10 @@ uint32_t micros(); uint32_t millis(); uint64_t millis_64(); +void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming) +void arch_feed_wdt(); +uint32_t arch_get_cpu_cycle_count(); + } // namespace esphome #endif // USE_ZEPHYR