diff --git a/components/esp_system/CMakeLists.txt b/components/esp_system/CMakeLists.txt index cc435662c90..3e7be90fdeb 100644 --- a/components/esp_system/CMakeLists.txt +++ b/components/esp_system/CMakeLists.txt @@ -5,6 +5,7 @@ idf_build_get_property(esp_tee_build ESP_TEE_BUILD) if(${target} STREQUAL "linux") idf_component_register(SRCS "esp_system.c" "esp_err.c" + "esp_sys_event.c" "port/soc/linux/reset_reason.c" "port/soc/linux/system_internal.c" "port/esp_system_linux.c" @@ -46,6 +47,7 @@ else() list(APPEND srcs "crosscore_int.c" "esp_ipc.c" "esp_err.c" + "esp_sys_event.c" "freertos_hooks.c" "panic.c" "esp_system.c" diff --git a/components/esp_system/esp_sys_event.c b/components/esp_system/esp_sys_event.c new file mode 100644 index 00000000000..7fb4139bffb --- /dev/null +++ b/components/esp_system/esp_sys_event.c @@ -0,0 +1,234 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_private/esp_sys_event_internal.h" +#include "freertos/FreeRTOS.h" +#include +#include +#include + +// Linker symbols for static handler arrays. On macOS these are resolved by +// the portable section helpers in esp_attr.h. The extra _DECL_/_GET_* +// indirections force expansion of _ESP_SYS_EVENT_SECTION_ before it is +// token-pasted by _SECTION_ATTR_SYMBOL_DECL_GENERIC / _SECTION_START / _SECTION_END. +#define _ESP_SYS_EVENT_DECL_EXTERN(SECTION) \ + _SECTION_ATTR_SYMBOL_DECL_GENERIC(esp_sys_event_handler_t, SECTION) +#define _ESP_SYS_EVENT_GET_START(SECTION) _SECTION_START(SECTION) +#define _ESP_SYS_EVENT_GET_END(SECTION) _SECTION_END(SECTION) + +#define ESP_SYS_EVENT_DEF(ID) \ + _ESP_SYS_EVENT_DECL_EXTERN(_ESP_SYS_EVENT_SECTION_##ID) +#include "esp_sys_event_list.inc" +#undef ESP_SYS_EVENT_DEF + +IRAM_ATTR esp_sys_event_iter_t esp_sys_event_get_static_handlers(esp_sys_event_id_t id) +{ + switch (id) { +#define ESP_SYS_EVENT_DEF(ID) \ + case ESP_SYS_EVENT_##ID: \ + return (esp_sys_event_iter_t) { \ + .start = _ESP_SYS_EVENT_GET_START(_ESP_SYS_EVENT_SECTION_##ID), \ + .end = _ESP_SYS_EVENT_GET_END(_ESP_SYS_EVENT_SECTION_##ID), \ + }; +#include "esp_sys_event_list.inc" +#undef ESP_SYS_EVENT_DEF + default: + return (esp_sys_event_iter_t) { + NULL, NULL + }; + } +} + +typedef STAILQ_HEAD(esp_sys_event_dynamic_list, esp_sys_event_dynamic_node) esp_sys_event_dynamic_list_t; + +static esp_sys_event_dynamic_list_t s_dynamic_handlers[ESP_SYS_EVENT_MAX] = { +#define ESP_SYS_EVENT_DEF(ID) \ + [ESP_SYS_EVENT_##ID] = STAILQ_HEAD_INITIALIZER(s_dynamic_handlers[ESP_SYS_EVENT_##ID]), +#include "esp_sys_event_list.inc" +#undef ESP_SYS_EVENT_DEF +}; + +static portMUX_TYPE s_dynamic_lock = portMUX_INITIALIZER_UNLOCKED; + +// Number of triggers currently iterating each dynamic list. Bumped atomically +// at trigger entry/exit so register/unregister can know whether it is safe to +// physically free tombstoned nodes (no concurrent iteration). +static atomic_int s_triggers_active[ESP_SYS_EVENT_MAX]; + +// Physically remove every tombstoned (handler == NULL) node from the dynamic +// list and free it. Caller must hold s_dynamic_lock and must have verified +// that s_triggers_active[id] == 0; otherwise a concurrent trigger could be +// dereferencing a node we are about to free. +static void free_dead_nodes_locked(esp_sys_event_dynamic_list_t *list) +{ + esp_sys_event_dynamic_node_t *node, *tmp; + STAILQ_FOREACH_SAFE(node, list, next, tmp) { + if (node->handler == NULL) { + STAILQ_REMOVE(list, node, esp_sys_event_dynamic_node, next); + free(node); + } + } +} + +static esp_sys_event_dynamic_node_t *find_dynamic_node_locked(esp_sys_event_dynamic_list_t *list, + esp_sys_event_fn_t handler, + void *user_arg) +{ + esp_sys_event_dynamic_node_t *node; + STAILQ_FOREACH(node, list, next) { + if (node->handler && node->handler == handler && node->user_arg == user_arg) { + return node; + } + } + + return NULL; +} + +esp_err_t esp_sys_event_register(esp_sys_event_id_t id, esp_sys_event_fn_t handler, void *user_arg) +{ + if (id >= ESP_SYS_EVENT_MAX || handler == NULL) { + return ESP_ERR_INVALID_ARG; + } + + esp_sys_event_dynamic_node_t *node = malloc(sizeof(*node)); + if (!node) { + return ESP_ERR_NO_MEM; + } + + node->handler = handler; + node->user_arg = user_arg; + + esp_sys_event_dynamic_list_t *list = &s_dynamic_handlers[id]; + portENTER_CRITICAL(&s_dynamic_lock); + if (atomic_load(&s_triggers_active[id]) == 0) { + free_dead_nodes_locked(list); + } + if (find_dynamic_node_locked(list, handler, user_arg)) { + portEXIT_CRITICAL(&s_dynamic_lock); + free(node); + return ESP_ERR_INVALID_STATE; + } + STAILQ_INSERT_TAIL(list, node, next); + portEXIT_CRITICAL(&s_dynamic_lock); + + return ESP_OK; +} + +esp_err_t esp_sys_event_unregister(esp_sys_event_id_t id, esp_sys_event_fn_t handler, void *user_arg) +{ + if (id >= ESP_SYS_EVENT_MAX || handler == NULL) { + return ESP_ERR_INVALID_ARG; + } + + esp_sys_event_dynamic_list_t *list = &s_dynamic_handlers[id]; + portENTER_CRITICAL(&s_dynamic_lock); + esp_sys_event_dynamic_node_t *node = find_dynamic_node_locked(list, handler, user_arg); + if (node) { + // Tombstone first so any in-flight trigger skips this node. + node->handler = NULL; + if (atomic_load(&s_triggers_active[id]) == 0) { + free_dead_nodes_locked(list); + } + portEXIT_CRITICAL(&s_dynamic_lock); + return ESP_OK; + } + portEXIT_CRITICAL(&s_dynamic_lock); + + return ESP_ERR_NOT_FOUND; +} + +void esp_sys_event_trigger_dynamic(esp_sys_event_id_t id, void *ctx) +{ + if (id >= ESP_SYS_EVENT_MAX) { + return; + } + + esp_sys_event_dynamic_list_t *list = &s_dynamic_handlers[id]; + + // Bracket the dispatch with an active-trigger counter so that + // register/unregister know not to free nodes while we iterate. The counter + // touch is atomic and ISR-safe; handler dispatch itself runs lock-free so + // handlers are free to block (e.g. flushing UART at shutdown). Tombstoned + // (handler == NULL) nodes are skipped. + atomic_fetch_add(&s_triggers_active[id], 1); + + esp_sys_event_dynamic_node_t *node; + STAILQ_FOREACH(node, list, next) { + if (node->handler) { + node->handler(node->user_arg, ctx); + } + } + + atomic_fetch_sub(&s_triggers_active[id], 1); +} + +IRAM_ATTR void esp_sys_event_trigger_static(esp_sys_event_id_t id, void *ctx) +{ + if (id >= ESP_SYS_EVENT_MAX) { + return; + } + + const esp_sys_event_iter_t info = esp_sys_event_get_static_handlers(id); + if (info.start == NULL || info.end == NULL) { + return; + } + + for (const esp_sys_event_handler_t *h = info.start; h < info.end; ++h) { + h->handler(NULL, ctx); + } +} + +void esp_sys_event_trigger(esp_sys_event_id_t id, void *ctx) +{ + esp_sys_event_trigger_static(id, ctx); + esp_sys_event_trigger_dynamic(id, ctx); +} + +// Event names indexed by id, generated from esp_sys_event_list.inc. +static const char *const s_event_names[] = { +#define ESP_SYS_EVENT_DEF(ID) [ESP_SYS_EVENT_##ID] = #ID, +#include "esp_sys_event_list.inc" +#undef ESP_SYS_EVENT_DEF +}; + +esp_err_t esp_sys_event_handler_dump(esp_sys_event_id_t id, FILE *stream) +{ + if (id >= ESP_SYS_EVENT_MAX) { + return ESP_ERR_INVALID_ARG; + } + if (stream == NULL) { + return ESP_OK; + } + + const esp_sys_event_iter_t info = esp_sys_event_get_static_handlers(id); + + fprintf(stream, "Event %s:\n", s_event_names[id]); + + if (info.start != NULL && info.end != NULL) { + size_t i = 0; + for (const esp_sys_event_handler_t *h = info.start; h < info.end; ++h, i++) { + fprintf(stream, " static[%u] %p\n", (unsigned)i, (void *)h->handler); + } + } + + esp_sys_event_dynamic_list_t *list = &s_dynamic_handlers[id]; + + // Bump the active-trigger counter to keep register/unregister from + // freeing nodes while we walk the dynamic list lock-free. + atomic_fetch_add(&s_triggers_active[id], 1); + + size_t dyn_idx = 0; + esp_sys_event_dynamic_node_t *node; + STAILQ_FOREACH(node, list, next) { + if (node->handler) { + fprintf(stream, " dynamic[%u] %p\n", (unsigned)dyn_idx++, (void *)node->handler); + } + } + + atomic_fetch_sub(&s_triggers_active[id], 1); + + return ESP_OK; +} diff --git a/components/esp_system/esp_system.c b/components/esp_system/esp_system.c index b57a2631be7..353bc87906e 100644 --- a/components/esp_system/esp_system.c +++ b/components/esp_system/esp_system.c @@ -1,49 +1,52 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "esp_system.h" #include "esp_private/system_internal.h" +#include "esp_private/esp_sys_event_internal.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#define SHUTDOWN_HANDLERS_NO 5 - -static shutdown_handler_t shutdown_handlers[SHUTDOWN_HANDLERS_NO]; +// Adapter for esp_register_shutdown_handler(): the public API validates the +// handler before storing it in user_arg, and SHUTDOWN does not pass a ctx. +static esp_err_t legacy_shutdown_trampoline(void *user_arg, void *ctx) +{ + (void)ctx; + shutdown_handler_t fn = (shutdown_handler_t)user_arg; + fn(); + return ESP_OK; +} esp_err_t esp_register_shutdown_handler(shutdown_handler_t handler) { - for (int i = 0; i < SHUTDOWN_HANDLERS_NO; i++) { - if (shutdown_handlers[i] == handler) { - return ESP_ERR_INVALID_STATE; - } else if (shutdown_handlers[i] == NULL) { - shutdown_handlers[i] = handler; - return ESP_OK; - } + if (handler == NULL) { + return ESP_ERR_INVALID_ARG; } - return ESP_ERR_NO_MEM; + return esp_sys_event_register(ESP_SYS_EVENT_SHUTDOWN, + legacy_shutdown_trampoline, + (void *)handler); } esp_err_t esp_unregister_shutdown_handler(shutdown_handler_t handler) { - for (int i = 0; i < SHUTDOWN_HANDLERS_NO; i++) { - if (shutdown_handlers[i] == handler) { - shutdown_handlers[i] = NULL; - return ESP_OK; - } + if (handler == NULL) { + return ESP_ERR_INVALID_ARG; } - return ESP_ERR_INVALID_STATE; + esp_err_t err = esp_sys_event_unregister(ESP_SYS_EVENT_SHUTDOWN, + legacy_shutdown_trampoline, + (void *)handler); + if (err == ESP_ERR_NOT_FOUND) { + return ESP_ERR_INVALID_STATE; + } + return err; } void esp_restart(void) { - for (int i = SHUTDOWN_HANDLERS_NO - 1; i >= 0; i--) { - if (shutdown_handlers[i]) { - shutdown_handlers[i](); - } - } + esp_sys_event_trigger(ESP_SYS_EVENT_SHUTDOWN, NULL); #if ( ( CONFIG_FREERTOS_SMP ) && ( !CONFIG_FREERTOS_UNICORE ) ) //Note: Scheduler suspension behavior changed in FreeRTOS SMP diff --git a/components/esp_system/include/esp_private/esp_sys_event.h b/components/esp_system/include/esp_private/esp_sys_event.h new file mode 100644 index 00000000000..9ca005cef54 --- /dev/null +++ b/components/esp_system/include/esp_private/esp_sys_event.h @@ -0,0 +1,84 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_attr.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file esp_private/esp_sys_event.h + * @brief Static-registration primitive for the private esp_sys_event framework + * + * This private header exposes the generic framework building blocks used by + * internal per-event registration macros. + * + * User code should use the per-event public wrappers (for example, + * esp_register_shutdown_handler and ESP_SHUTDOWN_HANDLER_REGISTER) rather than + * registering against the framework directly. + */ + +/** + * @brief System event handler function signature + * + * - user_arg: per-registration context (NULL for static handlers) + * - ctx: per-trigger event context passed at dispatch time + */ +typedef esp_err_t (*esp_sys_event_fn_t)(void *user_arg, void *ctx); + +/** + * @brief System event handler descriptor for static registration + * + * One instance is emitted per static registration into the event's linker + * section by _ESP_SYS_EVENT_REGISTER. + */ +typedef struct { + esp_sys_event_fn_t handler; /*!< Handler function pointer */ +} esp_sys_event_handler_t; + +/* Pull in the _ESP_SYS_EVENT_SECTION_ lookup macros from the master + * event table. ESP_SYS_EVENT_DEF is a no-op here -- we only want the section + * name #defines on this pass. */ +#define ESP_SYS_EVENT_DEF(ID) +#include "esp_sys_event_list.inc" +#undef ESP_SYS_EVENT_DEF + +#define _ESP_SYS_EVENT_STRINGIFY_INNER(x) #x +#define _ESP_SYS_EVENT_STRINGIFY(x) _ESP_SYS_EVENT_STRINGIFY_INNER(x) + +/** + * @brief Register a static (link-time) handler for an event. + * + * Building block used by internal per-event wrappers (for example, + * ESP_SYSTEM_INIT_CORE). + * + * The handler will be sorted by priority at link time and called with + * (NULL, ctx) at trigger time. + * + * @param EVENT Event token -- must match an entry in esp_sys_event_list.inc. + * Used to look up the linker section via + * _ESP_SYS_EVENT_SECTION_ and to disambiguate the + * descriptor symbol so the same `fn` can register against + * different events from the same translation unit. + * @param prio Priority (lower runs first). + * @param fn Handler function name. The macro emits a forward + * declaration and opens a function definition; the body + * must follow immediately at the call site. + */ +#define _ESP_SYS_EVENT_REGISTER(EVENT, prio, fn, ...) \ + static esp_err_t __VA_ARGS__ fn(void *user_arg, void *ctx); \ + static _SECTION_ATTR_IMPL_GENERIC( \ + _ESP_SYS_EVENT_STRINGIFY(_ESP_SYS_EVENT_SECTION_##EVENT), prio) \ + esp_sys_event_handler_t _esysev_##EVENT##_##fn = {.handler = fn}; \ + static esp_err_t __VA_ARGS__ fn(void *user_arg, void *ctx) + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_system/include/esp_private/esp_sys_event_internal.h b/components/esp_system/include/esp_private/esp_sys_event_internal.h new file mode 100644 index 00000000000..ba620dad565 --- /dev/null +++ b/components/esp_system/include/esp_private/esp_sys_event_internal.h @@ -0,0 +1,181 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_private/esp_sys_event.h" +#include "sys/queue.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * + * Event IDs, dynamic register/unregister, trigger functions, iteration, + * and the dump helper. Internal to IDF; per-event public surface (if any) + * is provided by separate event-specific headers or wrappers. + */ + +/** + * @brief System event identifier + * + * Auto-generated from esp_sys_event_list.inc. To add or remove events, + * edit the .inc file — the enum, linker symbol externs, and dispatch + * table all stay in sync automatically. + */ +typedef enum { +#define ESP_SYS_EVENT_DEF(ID) ESP_SYS_EVENT_##ID, +#include "esp_sys_event_list.inc" +#undef ESP_SYS_EVENT_DEF + ESP_SYS_EVENT_MAX +} esp_sys_event_id_t; + +/** + * @brief Node structure for dynamically registered event handlers + */ +typedef struct esp_sys_event_dynamic_node { + esp_sys_event_fn_t handler; /*!< Handler function pointer */ + void *user_arg; /*!< Per-registration context */ + STAILQ_ENTRY(esp_sys_event_dynamic_node) next; /*!< Link to next node */ +} esp_sys_event_dynamic_node_t; + +/** + * @brief Register a dynamic event handler + * + * Registers a handler function to be called when the specified event is + * triggered. Dynamic handlers are called after static handlers. + * The handler receives (user_arg, ctx) where ctx is the event context + * passed at trigger time. + * + * @param id Event ID + * @param handler Handler function to register + * @param user_arg Per-registration context passed as first arg to handler + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if id is invalid or handler is NULL + * - ESP_ERR_INVALID_STATE if handler is already registered with same + * user_arg + * - ESP_ERR_NO_MEM if memory allocation failed + */ +esp_err_t esp_sys_event_register(esp_sys_event_id_t id, + esp_sys_event_fn_t handler, void *user_arg); + +/** + * @brief Unregister a dynamic event handler + * + * Removes a previously registered handler for the specified event. + * Matches by both handler function pointer and user_arg. + * + * @param id Event ID + * @param handler Handler function to unregister + * @param user_arg The same user_arg that was used during registration + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if id is invalid or handler is NULL + * - ESP_ERR_NOT_FOUND if the handler was not found + */ +esp_err_t esp_sys_event_unregister(esp_sys_event_id_t id, + esp_sys_event_fn_t handler, void *user_arg); + +/** + * @brief Trigger all dynamic handlers for an event + * + * Iterates over dynamically registered handlers for the event, calling each + * with (user_arg, ctx). All handlers are called regardless of individual + * return values. + * + * @param id Event ID + * @param ctx Event context pointer passed to each handler + */ +void esp_sys_event_trigger_dynamic(esp_sys_event_id_t id, void *ctx); + +/** + * @brief Trigger all static (link-time) handlers for an event + * + * The dispatcher is placed in IRAM, but cache-disabled use is only safe for + * events whose handler descriptor sections and registered handlers are also + * placed in RAM/IRAM. Future events which need cache-disabled dispatch must + * define their linker placement and registration macro accordingly. + * + * Iterates over statically registered handlers for the event, calling each + * with (NULL, ctx). All handlers are called regardless of individual return + * values. + * + * @param id Event ID + * @param ctx Context pointer to pass to handlers + */ +void esp_sys_event_trigger_static(esp_sys_event_id_t id, void *ctx); + +/** + * @brief Trigger all handlers (static + dynamic) for an event + * + * Convenience wrapper that calls esp_sys_event_trigger_static() followed by + * esp_sys_event_trigger_dynamic(). Both receive the same ctx. + * + * @param id Event ID + * @param ctx Context pointer to pass to all handlers + */ +void esp_sys_event_trigger(esp_sys_event_id_t id, void *ctx); + +/** + * @brief Iterator bounds for static handler iteration + */ +typedef struct { + const esp_sys_event_handler_t *start; + const esp_sys_event_handler_t *end; +} esp_sys_event_iter_t; + +/** + * @brief Get static handler array bounds for an event + * + * Returns the start and end of the static handler array for the specified + * event. This allows custom iteration logic (e.g., feeding watchdog between + * handlers in panic context). Returns {NULL, NULL} for an invalid id. + * + * @param id Event ID + * @return esp_sys_event_iter_t with start/end pointers ({NULL, NULL} on invalid + * id) + */ +esp_sys_event_iter_t esp_sys_event_get_static_handlers(esp_sys_event_id_t id); + +/** + * @brief Iterate over all static handlers for an event + * + * @param h Variable name for the handler pointer + * @param id Event ID (esp_sys_event_id_t) + * + * Example: + * @code + * ESP_SYS_EVENT_FOREACH(h, ESP_SYS_EVENT_SHUTDOWN) { + * h->handler(NULL, ctx); + * } + * @endcode + */ +#define ESP_SYS_EVENT_FOREACH(h, id) \ + for (esp_sys_event_iter_t _iter = esp_sys_event_get_static_handlers(id), \ + *_once = &_iter; \ + _once; _once = NULL) \ + for (const esp_sys_event_handler_t *h = _iter.start; h < _iter.end; ++h) + +/** + * @brief Dump every handler registered to the given event to a stream + * + * Prints each static handler and each dynamic handler (function pointer) for + * the event. Output format is suitable for debugging. + * + * @param id Event ID + * @param stream Stream to print to (e.g. stdout), or NULL to skip output + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if id is invalid + */ +esp_err_t esp_sys_event_handler_dump(esp_sys_event_id_t id, FILE *stream); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_system/include/esp_private/esp_sys_event_system_init.h b/components/esp_system/include/esp_private/esp_sys_event_system_init.h new file mode 100644 index 00000000000..902255c034f --- /dev/null +++ b/components/esp_system/include/esp_private/esp_sys_event_system_init.h @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_cpu.h" +#include "esp_private/esp_sys_event.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file esp_sys_event_system_init.h + * @brief System initialization events using esp_sys_event framework + * + * This header provides macros for registering system initialization functions + * that replace the legacy ESP_SYSTEM_INIT_FN infrastructure. + * + * There are two system init event lists: + * - system_init_core: CORE stage, runs on core 0 (secondary cores are not + * started yet at this point, so there is no per-CPU variant) + * - system_init_secondary: SECONDARY stage, runs on each core; core-scoped + * registration macros use wrappers that skip on cores where they should not + * run, preserving one priority-ordered list for the whole secondary stage. + * + * Migration from ESP_SYSTEM_INIT_FN: + * - ESP_SYSTEM_INIT_FN(fn, CORE, BIT(0), prio) -> ESP_SYSTEM_INIT_CORE(fn, prio) + * - ESP_SYSTEM_INIT_FN(fn, SECONDARY, BIT(0), prio) -> ESP_SYSTEM_INIT_SECONDARY(fn, prio) + * - ESP_SYSTEM_INIT_FN(fn, SECONDARY, ALL_CORES, prio) -> ESP_SYSTEM_INIT_SECONDARY_PERCPU(fn, prio) + */ + +/** + * @brief Register a CORE stage init function (runs once on core 0) + * + * This is the most common init function type. The function runs once during + * the CORE initialization stage on CPU 0. + * + * @param fn Function name + * @param prio Priority (lower runs first) + */ +#define ESP_SYSTEM_INIT_CORE(fn, prio) \ + _ESP_SYS_EVENT_REGISTER(SYSTEM_INIT_CORE, prio, fn) + +#define _ESP_SYSTEM_INIT_SECONDARY_RAW(fn, prio) \ + _ESP_SYS_EVENT_REGISTER(SYSTEM_INIT_SECONDARY, prio, fn) + +#define _ESP_SYSTEM_INIT_SECONDARY_ON_CORE(fn, prio, core_id) \ + static esp_err_t fn(void *user_arg, void *ctx); \ + _ESP_SYSTEM_INIT_SECONDARY_RAW(__esp_system_init_secondary_##fn, prio) \ + { \ + if (esp_cpu_get_core_id() != (core_id)) { \ + return ESP_OK; \ + } \ + return fn(user_arg, ctx); \ + } \ + static esp_err_t fn(void *user_arg, void *ctx) + +/** + * @brief Register a SECONDARY stage init function (runs once on core 0) + * + * This is the most common init function type for secondary stage. The function + * runs once during the SECONDARY initialization stage on CPU 0. + * + * @param fn Function name + * @param prio Priority (lower runs first) + */ +#define ESP_SYSTEM_INIT_SECONDARY(fn, prio) \ + _ESP_SYSTEM_INIT_SECONDARY_ON_CORE(fn, prio, 0) + +/** + * @brief Register a SECONDARY stage init function that runs on each core + * + * Use this for init functions that must run on every core during the + * SECONDARY initialization stage. + * + * @param fn Function name + * @param prio Priority (lower runs first) + */ +#define ESP_SYSTEM_INIT_SECONDARY_PERCPU(fn, prio) \ + _ESP_SYSTEM_INIT_SECONDARY_RAW(fn, prio) + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_system/include/esp_private/startup_internal.h b/components/esp_system/include/esp_private/startup_internal.h index c86a1701860..4abd6ab4e91 100644 --- a/components/esp_system/include/esp_private/startup_internal.h +++ b/components/esp_system/include/esp_private/startup_internal.h @@ -10,6 +10,8 @@ #include "esp_err.h" #include "esp_bit_defs.h" #include "esp_cpu.h" +#include "esp_private/esp_sys_event_system_init.h" + #include "soc/soc_caps.h" #include "sdkconfig.h" @@ -36,24 +38,12 @@ extern sys_startup_fn_t const g_startup_fn[1]; void startup_resume_other_cores(void); #endif -/** - * Internal structure describing ESP_SYSTEM_INIT_FN startup functions - */ -typedef struct { - esp_err_t (*fn)(void); /*!< Pointer to the startup function */ - uint16_t cores; /*!< Bit mask of cores where the function has to be called */ - uint16_t stage; /*!< Init stage number (0 or 1) */ -} esp_system_init_fn_t; - -#define ESP_SYSTEM_INIT_STAGE_CORE 0 -#define ESP_SYSTEM_INIT_STAGE_SECONDARY 1 - /** * @brief Define a system initialization function which will be executed on the specified cores * * @param f function name (identifier) * @param stage_ init stage name (CORE or SECONDARY) - * @param c bit mask of cores to execute the function on (ex. if BIT0 is set, the function + * @param c bit mask of cores to execute the SECONDARY function on (ex. if BIT0 is set, the function * will be executed on CPU 0, if BIT1 is set - on CPU 1, and so on) * @param priority integer, priority of the initialization function. Higher values mean that * the function will be executed later in the process. @@ -67,16 +57,38 @@ typedef struct { * get optimized out by the compiler or discarded by the linker if the related feature is used. * It is, on the other hand, a good practice to make sure the initialization function does get * discarded if the related feature is not used. + * + * @note Compatibility wrapper: legacy users continue to register with + * `ESP_SYSTEM_INIT_FN`, but the generated adapters run through the + * `esp_sys_event`-based init stages. + * + * CORE-stage handlers register directly into `SYSTEM_INIT_CORE` (only + * core 0 is running at that point). SECONDARY-stage handlers register + * into the shared `SYSTEM_INIT_SECONDARY` list so all secondary handlers + * are priority-ordered together; a runtime core-mask check skips cores + * not in `c`, matching the legacy behaviour. */ +#define _ESP_SYSTEM_INIT_COMPAT_CORE(f, c, priority) \ + ESP_SYSTEM_INIT_CORE(__esp_system_init_##f, priority) \ + { \ + (void)user_arg; (void)ctx; \ + return __esp_system_init_fn_##f(); \ + } + +#define _ESP_SYSTEM_INIT_COMPAT_SECONDARY(f, c, priority) \ + _ESP_SYSTEM_INIT_SECONDARY_RAW(__esp_system_init_##f, priority) \ + { \ + (void)user_arg; (void)ctx; \ + if (((c) & BIT(esp_cpu_get_core_id())) == 0) { \ + return ESP_OK; \ + } \ + return __esp_system_init_fn_##f(); \ + } + #define ESP_SYSTEM_INIT_FN(f, stage_, c, priority, ...) \ - static esp_err_t __VA_ARGS__ __esp_sys_init_fn_##f(void); \ - static _SECTION_ATTR_IMPL_GENERIC("esp_sys_init_fn", priority) \ - esp_system_init_fn_t esp_sys_init_fn_##f = { \ - .fn = ( __esp_sys_init_fn_##f), \ - .cores = (c), \ - .stage = ESP_SYSTEM_INIT_STAGE_##stage_ \ - }; \ - static esp_err_t __esp_sys_init_fn_##f(void) + static esp_err_t __VA_ARGS__ __esp_system_init_fn_##f(void); \ + _ESP_SYSTEM_INIT_COMPAT_##stage_(f, c, priority) \ + static esp_err_t __esp_system_init_fn_##f(void) #ifdef CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE #define ESP_SYSTEM_INIT_ALL_CORES BIT(0) diff --git a/components/esp_system/include/esp_sys_event_list.inc b/components/esp_system/include/esp_sys_event_list.inc new file mode 100644 index 00000000000..aa20ddf56ef --- /dev/null +++ b/components/esp_system/include/esp_sys_event_list.inc @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file esp_sys_event_list.inc + * @brief Master table of all system events. Single source of truth. + * + * This file is included multiple times with different definitions of + * ESP_SYS_EVENT_DEF() to generate the enum, linker symbol externs, and + * the trigger dispatch table from one declaration each. + * + * To add a new event "FOO": + * 1. Add `#define _ESP_SYS_EVENT_SECTION_FOO esysev_foo` below (within + * the include guard). Keep the section name short so it fits within + * the Mach-O 16-character section-name limit. + * 2. Add `ESP_SYS_EVENT_DEF(FOO)` to the table at the bottom. + * 3. Add the corresponding entries to linker.lf and ld/linux/sections.ld + * for `esysev_foo`. + * 4. Optionally add a registration macro in the relevant private domain + * header using `_ESP_SYS_EVENT_REGISTER(FOO, prio, fn)`, or a + * public event-specific wrapper that does not expose the generic + * framework header. + * + * @note Enum values are assigned implicitly in the order below. Do not + * reorder unless you know nothing depends on a specific numeric + * value (no IDF code does today; values are not persisted). + */ + +#ifndef ESP_SYS_EVENT_SECTIONS_DEFINED +#define ESP_SYS_EVENT_SECTIONS_DEFINED +#define _ESP_SYS_EVENT_SECTION_SHUTDOWN esysev_shdn +#define _ESP_SYS_EVENT_SECTION_SYSTEM_INIT_CORE esysev_initc +#define _ESP_SYS_EVENT_SECTION_SYSTEM_INIT_SECONDARY esysev_inits +#endif + +/* ESP_SYS_EVENT_DEF(ENUM_SUFFIX) */ +ESP_SYS_EVENT_DEF(SHUTDOWN) +ESP_SYS_EVENT_DEF(SYSTEM_INIT_CORE) +ESP_SYS_EVENT_DEF(SYSTEM_INIT_SECONDARY) diff --git a/components/esp_system/include/esp_system.h b/components/esp_system/include/esp_system.h index c1c90362e45..3285e6bc9e1 100644 --- a/components/esp_system/include/esp_system.h +++ b/components/esp_system/include/esp_system.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -46,28 +46,54 @@ typedef enum { typedef void (*shutdown_handler_t)(void); /** - * @brief Register shutdown handler - * - * This function allows you to register a handler that gets invoked before - * the application is restarted using esp_restart function. - * @param handle function to execute on restart - * @return - * - ESP_OK on success - * - ESP_ERR_INVALID_STATE if the handler has already been registered - * - ESP_ERR_NO_MEM if no more shutdown handler slots are available - */ + * @brief Register a shutdown handler + * + * Registers a handler invoked before the application is restarted via + * esp_restart(). The dispatch order between handlers registered through + * this function is unspecified; if a deterministic order is required, + * use ESP_SHUTDOWN_HANDLER_REGISTER instead, which dispatches in priority + * order and runs before any handlers registered here. + * + * The number of registrations is bounded only by available heap. + * + * @param handle Function to execute at shutdown. Must not be NULL. + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if `handle` is NULL + * - ESP_ERR_INVALID_STATE if the handler is already registered + * - ESP_ERR_NO_MEM if memory allocation failed + */ esp_err_t esp_register_shutdown_handler(shutdown_handler_t handle); /** - * @brief Unregister shutdown handler - * - * This function allows you to unregister a handler which was previously - * registered using esp_register_shutdown_handler function. - * - ESP_OK on success - * - ESP_ERR_INVALID_STATE if the given handler hasn't been registered before - */ + * @brief Unregister a shutdown handler + * + * Removes a handler previously registered via esp_register_shutdown_handler(). + * + * @param handle The handler to unregister. Must not be NULL. + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if `handle` is NULL + * - ESP_ERR_INVALID_STATE if the handler is not currently registered + */ esp_err_t esp_unregister_shutdown_handler(shutdown_handler_t handle); +/** + * @brief Register a static (link-time) shutdown handler + * + * Handlers registered with this macro are called at shutdown time in + * priority order (lower priority values run first), before any handlers + * registered dynamically via esp_register_shutdown_handler(). + * + * @param fn Handler function name (signature: esp_err_t fn(void *user_arg, void *ctx)) + * @param prio Priority (lower runs first) + */ +#define ESP_SHUTDOWN_HANDLER_REGISTER(fn, prio) \ + static esp_err_t fn(void *user_arg, void *ctx); \ + static _SECTION_ATTR_IMPL_GENERIC("esysev_shdn", prio) \ + struct { esp_err_t (*handler)(void *user_arg, void *ctx); } _esysev_SHUTDOWN_##fn = { .handler = fn }; \ + static esp_err_t fn(void *user_arg, void *ctx) + /** * @brief Restart PRO and APP CPUs. * diff --git a/components/esp_system/ld/esp32/sections.ld.in b/components/esp_system/ld/esp32/sections.ld.in index f420b9ed9c4..b4f5835e401 100644 --- a/components/esp_system/ld/esp32/sections.ld.in +++ b/components/esp_system/ld/esp32/sections.ld.in @@ -367,11 +367,6 @@ SECTIONS KEEP (*(.reserved_memory_address)) soc_reserved_memory_region_end = ABSOLUTE(.); - /* System init functions registered via ESP_SYSTEM_INIT_FN */ - ALIGNED_SYMBOL(4, _esp_sys_init_fn_start) - KEEP (*(SORT_BY_INIT_PRIORITY(.esp_sys_init_fn.*))) - _esp_sys_init_fn_end = ABSOLUTE(.); - _rodata_end = ABSOLUTE(.); /* Literals are also RO data. */ diff --git a/components/esp_system/ld/esp32s2/sections.ld.in b/components/esp_system/ld/esp32s2/sections.ld.in index 0f67740208b..dab2c1776b4 100644 --- a/components/esp_system/ld/esp32s2/sections.ld.in +++ b/components/esp_system/ld/esp32s2/sections.ld.in @@ -371,11 +371,6 @@ SECTIONS KEEP (*(.reserved_memory_address)) soc_reserved_memory_region_end = ABSOLUTE(.); - /* System init functions registered via ESP_SYSTEM_INIT_FN */ - ALIGNED_SYMBOL(4, _esp_sys_init_fn_start) - KEEP (*(SORT_BY_INIT_PRIORITY(.esp_sys_init_fn.*))) - _esp_sys_init_fn_end = ABSOLUTE(.); - _rodata_end = ABSOLUTE(.); /* Literals are also RO data. */ diff --git a/components/esp_system/ld/esp32s3/sections.ld.in b/components/esp_system/ld/esp32s3/sections.ld.in index ab323eee536..d065d59dc65 100644 --- a/components/esp_system/ld/esp32s3/sections.ld.in +++ b/components/esp_system/ld/esp32s3/sections.ld.in @@ -380,11 +380,6 @@ SECTIONS KEEP (*(.reserved_memory_address)) soc_reserved_memory_region_end = ABSOLUTE(.); - /* System init functions registered via ESP_SYSTEM_INIT_FN */ - ALIGNED_SYMBOL(4, _esp_sys_init_fn_start) - KEEP (*(SORT_BY_INIT_PRIORITY(.esp_sys_init_fn.*))) - _esp_sys_init_fn_end = ABSOLUTE(.); - _rodata_end = ABSOLUTE(.); /* Literals are also RO data. */ diff --git a/components/esp_system/ld/ld.flash.sections b/components/esp_system/ld/ld.flash.sections index 22cc9e2ae47..5c88a7921c7 100644 --- a/components/esp_system/ld/ld.flash.sections +++ b/components/esp_system/ld/ld.flash.sections @@ -134,11 +134,6 @@ KEEP (*(.reserved_memory_address)) soc_reserved_memory_region_end = ABSOLUTE(.); - /* System init functions registered via ESP_SYSTEM_INIT_FN */ - ALIGNED_SYMBOL(4, _esp_sys_init_fn_start) - KEEP (*(SORT_BY_INIT_PRIORITY(.esp_sys_init_fn.*))) - _esp_sys_init_fn_end = ABSOLUTE(.); - _rodata_end = ABSOLUTE(.); . = ALIGN(ALIGNOF(SECTION_AFTER_FLASH_RODATA)); } > flash_rodata_seg diff --git a/components/esp_system/ld/linux/sections.ld b/components/esp_system/ld/linux/sections.ld index 512d72e9f67..d97f773b8cc 100644 --- a/components/esp_system/ld/linux/sections.ld +++ b/components/esp_system/ld/linux/sections.ld @@ -5,11 +5,22 @@ */ SECTIONS { - .esp_sys_init_fn : + .esp_sys_event : { - PROVIDE(_esp_sys_init_fn_start = .); - KEEP(*(SORT(.esp_sys_init_fn*))) - PROVIDE(_esp_sys_init_fn_end = .); + . = ALIGN(4); + PROVIDE(_esysev_shdn_start = .); + KEEP(*(SORT_BY_INIT_PRIORITY(.esysev_shdn.*))) + PROVIDE(_esysev_shdn_end = .); + + . = ALIGN(4); + PROVIDE(_esysev_initc_start = .); + KEEP(*(SORT_BY_INIT_PRIORITY(.esysev_initc.*))) + PROVIDE(_esysev_initc_end = .); + + . = ALIGN(4); + PROVIDE(_esysev_inits_start = .); + KEEP(*(SORT_BY_INIT_PRIORITY(.esysev_inits.*))) + PROVIDE(_esysev_inits_end = .); } } INSERT AFTER .data; diff --git a/components/esp_system/linker.lf b/components/esp_system/linker.lf index 226113e4ce9..051fc57cffa 100644 --- a/components/esp_system/linker.lf +++ b/components/esp_system/linker.lf @@ -1,3 +1,34 @@ +# ============== esp_sys_event sections ============== + +[sections:esysev_shdn] +entries: + .esysev_shdn+ + +[sections:esysev_initc] +entries: + .esysev_initc+ + +[sections:esysev_inits] +entries: + .esysev_inits+ + +# Default placements (overridden below with ALIGN/SURROUND flags) +[scheme:esp_sys_event_default] +entries: + esysev_shdn -> flash_rodata + esysev_initc -> flash_rodata + esysev_inits -> flash_rodata + +[mapping:esp_sys_event] +archive: * +entries: + * (esp_sys_event_default); + esysev_shdn -> flash_rodata ALIGN(4, pre) KEEP() SORT(init_priority) SURROUND(esysev_shdn), + esysev_initc -> flash_rodata ALIGN(4, pre) KEEP() SORT(init_priority) SURROUND(esysev_initc), + esysev_inits -> flash_rodata ALIGN(4, pre) KEEP() SORT(init_priority) SURROUND(esysev_inits) + +# ============== esp_system component mappings ============== + [mapping:esp_system] archive: libesp_system.a entries: diff --git a/components/esp_system/startup.c b/components/esp_system/startup.c index 3b56183dcdb..60dcdf861e5 100644 --- a/components/esp_system/startup.c +++ b/components/esp_system/startup.c @@ -21,6 +21,7 @@ #include "esp_cpu.h" #include "esp_private/startup_internal.h" +#include "esp_private/esp_sys_event_internal.h" // Ensure that system configuration matches the underlying number of cores. // This should enable us to avoid checking for both every time. @@ -106,34 +107,16 @@ static void __do_global_ctors_1(void) } #endif // CONFIG_COMPILER_CXX_EXCEPTIONS && !CONFIG_IDF_TARGET_LINUX -/* declare the start and stop symbols surrounding the array of init functions - * registered by calling the system init function macros */ -_SECTION_ATTR_SYMBOL_DECL_GENERIC(esp_system_init_fn_t, esp_sys_init_fn) - -/** - * @brief Call component init functions defined using the system init function macros. - * The esp_system_init_fn_t structures describing these functions are collected into - * an array [_esp_sys_init_fn_start, _esp_sys_init_fn_end) by the - * linker. The functions are sorted by their priority value. - * The sequence of the init function calls (sorted by priority) is documented in - * system_init_fn.txt file. - * @param stage_num Stage number of the init function call (0, 1). - */ -__attribute__((no_sanitize_undefined)) /* TODO: IDF-8133 */ -static void do_system_init_fn(uint32_t stage_num) +static void do_system_init_event(esp_sys_event_id_t id) { - const esp_system_init_fn_t *p; - int core_id = esp_cpu_get_core_id(); - for (p = _SECTION_START(esp_sys_init_fn); p < _SECTION_END(esp_sys_init_fn); ++p) { - if (p->stage == stage_num && (p->cores & BIT(core_id)) != 0) { - // During core init, stdout is not initialized yet, so use early logging. - ESP_EARLY_LOGD(TAG, "calling init function: %p on core: %d", p->fn, core_id); - esp_err_t err = (*(p->fn))(); - if (err != ESP_OK) { - ESP_EARLY_LOGE(TAG, "init function %p has failed (0x%x), aborting", p->fn, err); - abort(); - } + ESP_SYS_EVENT_FOREACH(h, id) { + // During core init, stdout is not initialized yet, so use early logging. + ESP_EARLY_LOGD(TAG, "calling init function: %p on core: %d", (void *)h->handler, core_id); + esp_err_t err = h->handler(NULL, NULL); + if (err != ESP_OK) { + ESP_EARLY_LOGE(TAG, "init function %p has failed (0x%x), aborting", (void *)h->handler, err); + abort(); } } @@ -155,7 +138,9 @@ static void esp_startup_start_app_other_cores_default(void) */ static void ESP_SYSTEM_IRAM_ATTR start_cpu_other_cores_default(void) { - do_system_init_fn(ESP_SYSTEM_INIT_STAGE_SECONDARY); + // Run secondary init handlers for this core. Core-scoped handlers skip + // themselves inside their registration wrappers. + do_system_init_event(ESP_SYS_EVENT_SYSTEM_INIT_SECONDARY); while (!s_system_full_inited) { esp_rom_delay_us(100); @@ -167,7 +152,10 @@ static void ESP_SYSTEM_IRAM_ATTR start_cpu_other_cores_default(void) static void do_core_init(void) { - do_system_init_fn(ESP_SYSTEM_INIT_STAGE_CORE); + // Run core init handlers (core 0 only, before cache/MMU is configured). + // Secondary cores are not started yet at this point, so there is no + // per-CPU CORE-stage event. + do_system_init_event(ESP_SYS_EVENT_SYSTEM_INIT_CORE); } static void do_secondary_init(void) @@ -178,10 +166,10 @@ static void do_secondary_init(void) startup_resume_other_cores(); #endif - // Execute initialization functions esp_system_init_fn_t assigned to the main core. While - // this is happening, all other cores are executing the initialization functions - // assigned to them since they have been resumed already. - do_system_init_fn(ESP_SYSTEM_INIT_STAGE_SECONDARY); + // Run secondary init handlers for core 0. While this is happening, all + // other cores are executing the same priority-ordered secondary list and + // skipping handlers whose core mask does not include them. + do_system_init_event(ESP_SYS_EVENT_SYSTEM_INIT_SECONDARY); #if !CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE // Wait for all cores to finish secondary init. diff --git a/components/esp_system/test_apps/.build-test-rules.yml b/components/esp_system/test_apps/.build-test-rules.yml index 269ab7f173a..2fbac891772 100644 --- a/components/esp_system/test_apps/.build-test-rules.yml +++ b/components/esp_system/test_apps/.build-test-rules.yml @@ -5,6 +5,10 @@ components/esp_system/test_apps/cache_panic: - *common_components - spi_flash # esp_system is included by default +components/esp_system/test_apps/esp_sys_event: + depends_components: + - esp_system + components/esp_system/test_apps/esp_system_unity_tests: disable: - if: (CONFIG_NAME == "psram" and SOC_SPIRAM_SUPPORTED != 1) diff --git a/components/esp_system/test_apps/esp_sys_event/CMakeLists.txt b/components/esp_system/test_apps/esp_sys_event/CMakeLists.txt new file mode 100644 index 00000000000..a2ad8cde9a2 --- /dev/null +++ b/components/esp_system/test_apps/esp_sys_event/CMakeLists.txt @@ -0,0 +1,10 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +project(test_esp_sys_event) diff --git a/components/esp_system/test_apps/esp_sys_event/README.md b/components/esp_system/test_apps/esp_sys_event/README.md new file mode 100644 index 00000000000..fec55bd2b65 --- /dev/null +++ b/components/esp_system/test_apps/esp_sys_event/README.md @@ -0,0 +1,4 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- | + +Tests for the `esp_sys_event` framework: static link-time handler priority ordering, dynamic register/unregister semantics, per-event isolation, combined static + dynamic dispatch, and the handler dump helper. diff --git a/components/esp_system/test_apps/esp_sys_event/main/CMakeLists.txt b/components/esp_system/test_apps/esp_sys_event/main/CMakeLists.txt new file mode 100644 index 00000000000..7508d1da4f8 --- /dev/null +++ b/components/esp_system/test_apps/esp_sys_event/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS "test_app_main.c" + "test_esp_sys_event.c" + PRIV_INCLUDE_DIRS . + PRIV_REQUIRES "unity" "esp_system" + WHOLE_ARCHIVE) diff --git a/components/esp_system/test_apps/esp_sys_event/main/test_app_main.c b/components/esp_system/test_apps/esp_sys_event/main/test_app_main.c new file mode 100644 index 00000000000..402d3ca03e5 --- /dev/null +++ b/components/esp_system/test_apps/esp_sys_event/main/test_app_main.c @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define TEST_MEMORY_LEAK_THRESHOLD (-512) + +static size_t before_free_8bit; +static size_t before_free_32bit; + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %zu bytes free, After %zu bytes free (delta %zd)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + /* Wait for idle task to clean up */ + vTaskDelay(10 / portTICK_PERIOD_MS); + + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +void app_main(void) +{ + printf("Running esp_sys_event tests\n"); + unity_run_menu(); +} diff --git a/components/esp_system/test_apps/esp_sys_event/main/test_esp_sys_event.c b/components/esp_system/test_apps/esp_sys_event/main/test_esp_sys_event.c new file mode 100644 index 00000000000..dfc7855e23a --- /dev/null +++ b/components/esp_system/test_apps/esp_sys_event/main/test_esp_sys_event.c @@ -0,0 +1,344 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "esp_system.h" +#include "esp_private/esp_sys_event.h" +#include "esp_private/esp_sys_event_internal.h" +#include "esp_err.h" +#include "esp_log.h" + +static const char *TAG = "test_esp_sys_event"; + +// ============== Static SHUTDOWN handlers ============== +// +// SHUTDOWN is used as the static-handler test target because this test app +// never calls esp_restart(), so handlers registered here are inert except +// when a test deliberately triggers the SHUTDOWN event via the framework. +// This avoids needing a private test event with its own linker section. + +static int s_static_call_order[10]; +static int s_static_call_count; +static void *s_static_last_ctx; + +ESP_SHUTDOWN_HANDLER_REGISTER(test_shutdown_100, 100) +{ + (void)user_arg; + s_static_call_order[s_static_call_count++] = 100; + s_static_last_ctx = ctx; + ESP_LOGI(TAG, "test_shutdown_100 called, ctx=%p", ctx); + return ESP_OK; +} + +ESP_SHUTDOWN_HANDLER_REGISTER(test_shutdown_200, 200) +{ + (void)user_arg; + s_static_call_order[s_static_call_count++] = 200; + s_static_last_ctx = ctx; + ESP_LOGI(TAG, "test_shutdown_200 called, ctx=%p", ctx); + return ESP_OK; +} + +ESP_SHUTDOWN_HANDLER_REGISTER(test_shutdown_150, 150) +{ + (void)user_arg; + s_static_call_order[s_static_call_count++] = 150; + s_static_last_ctx = ctx; + ESP_LOGI(TAG, "test_shutdown_150 called, ctx=%p", ctx); + return ESP_OK; +} + +static void reset_static_state(void) +{ + s_static_call_count = 0; + memset(s_static_call_order, 0, sizeof(s_static_call_order)); + s_static_last_ctx = NULL; +} + +static bool is_test_shutdown_handler(esp_sys_event_fn_t handler) +{ + return handler == test_shutdown_100 + || handler == test_shutdown_150 + || handler == test_shutdown_200; +} + +static size_t count_test_shutdown_handlers(void) +{ + size_t count = 0; + + ESP_SYS_EVENT_FOREACH(h, ESP_SYS_EVENT_SHUTDOWN) { + if (is_test_shutdown_handler(h->handler)) { + count++; + } + } + + return count; +} + +// ============== Dynamic handlers ============== + +static int s_dynamic_handler_call_count; +static void *s_dynamic_last_arg; +static void *s_dynamic_last_ctx; + +static esp_err_t dynamic_handler_1(void *user_arg, void *ctx) +{ + s_dynamic_handler_call_count++; + s_dynamic_last_arg = user_arg; + s_dynamic_last_ctx = ctx; + ESP_LOGI(TAG, "dynamic_handler_1 called, user_arg=%p ctx=%p", user_arg, ctx); + return ESP_OK; +} + +static esp_err_t dynamic_handler_2(void *user_arg, void *ctx) +{ + s_dynamic_handler_call_count++; + s_dynamic_last_arg = user_arg; + s_dynamic_last_ctx = ctx; + ESP_LOGI(TAG, "dynamic_handler_2 called, user_arg=%p ctx=%p", user_arg, ctx); + return ESP_OK; +} + +static esp_err_t dynamic_handler_error(void *user_arg, void *ctx) +{ + (void)user_arg; (void)ctx; + s_dynamic_handler_call_count++; + ESP_LOGI(TAG, "dynamic_handler_error called, returning error"); + return ESP_FAIL; +} + +// ============== Test cases ============== + +TEST_CASE("esp_sys_event static registration - priority ordering", "[esp_sys_event]") +{ + reset_static_state(); + int test_ctx = 42; + + // Trigger via the IRAM-safe static-only entrypoint + esp_sys_event_trigger_static(ESP_SYS_EVENT_SHUTDOWN, &test_ctx); + + // All three ESP_SHUTDOWN_HANDLER_REGISTER entries from this file must run + TEST_ASSERT_EQUAL(3, s_static_call_count); + + // Sorted by priority: 100, 150, 200 + TEST_ASSERT_EQUAL(100, s_static_call_order[0]); + TEST_ASSERT_EQUAL(150, s_static_call_order[1]); + TEST_ASSERT_EQUAL(200, s_static_call_order[2]); + + // Context was forwarded + TEST_ASSERT_EQUAL_PTR(&test_ctx, s_static_last_ctx); +} + +TEST_CASE("ESP_SYS_EVENT_FOREACH - iterates static handlers", "[esp_sys_event]") +{ + int count = 0; + int test_handler_count = 0; + + ESP_SYS_EVENT_FOREACH(h, ESP_SYS_EVENT_SHUTDOWN) { + TEST_ASSERT_NOT_NULL(h->handler); + if (is_test_shutdown_handler(h->handler)) { + test_handler_count++; + } + count++; + } + + // Other components may also register static SHUTDOWN handlers. + TEST_ASSERT_GREATER_OR_EQUAL(3, count); + TEST_ASSERT_EQUAL(3, test_handler_count); +} + +TEST_CASE("esp_sys_event dynamic registration - register and trigger", "[esp_sys_event]") +{ + s_dynamic_handler_call_count = 0; + s_dynamic_last_arg = NULL; + s_dynamic_last_ctx = NULL; + + int user_data = 123; + int event_ctx = 456; + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_register(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, &user_data)); + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_register(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_2, &user_data)); + + esp_sys_event_trigger_dynamic(ESP_SYS_EVENT_SHUTDOWN, &event_ctx); + + TEST_ASSERT_EQUAL(2, s_dynamic_handler_call_count); + TEST_ASSERT_EQUAL_PTR(&user_data, s_dynamic_last_arg); + TEST_ASSERT_EQUAL_PTR(&event_ctx, s_dynamic_last_ctx); + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_unregister(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, &user_data)); + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_unregister(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_2, &user_data)); +} + +TEST_CASE("esp_sys_event dynamic registration - unregister", "[esp_sys_event]") +{ + s_dynamic_handler_call_count = 0; + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_register(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, NULL)); + + esp_sys_event_trigger_dynamic(ESP_SYS_EVENT_SHUTDOWN, NULL); + TEST_ASSERT_EQUAL(1, s_dynamic_handler_call_count); + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_unregister(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, NULL)); + + esp_sys_event_trigger_dynamic(ESP_SYS_EVENT_SHUTDOWN, NULL); + TEST_ASSERT_EQUAL(1, s_dynamic_handler_call_count); + + TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, esp_sys_event_unregister(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, NULL)); +} + +TEST_CASE("esp_sys_event dynamic registration - error handling", "[esp_sys_event]") +{ + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_sys_event_register(ESP_SYS_EVENT_MAX, dynamic_handler_1, NULL)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_sys_event_register(ESP_SYS_EVENT_SHUTDOWN, NULL, NULL)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_register(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, NULL)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_sys_event_register(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, NULL)); + // Same handler with different user_arg is allowed + int x; + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_register(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, &x)); + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_unregister(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_unregister(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, &x)); + + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_sys_event_unregister(ESP_SYS_EVENT_MAX, dynamic_handler_1, NULL)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_sys_event_unregister(ESP_SYS_EVENT_SHUTDOWN, NULL, NULL)); +} + +TEST_CASE("esp_sys_event dynamic registration - handler error does not abort dispatch", "[esp_sys_event]") +{ + s_dynamic_handler_call_count = 0; + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_register(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_error, NULL)); + + esp_sys_event_trigger_dynamic(ESP_SYS_EVENT_SHUTDOWN, NULL); + TEST_ASSERT_EQUAL(1, s_dynamic_handler_call_count); + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_unregister(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_error, NULL)); +} + +TEST_CASE("esp_sys_event dynamic registration - event isolation", "[esp_sys_event]") +{ + s_dynamic_handler_call_count = 0; + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_register(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_register(ESP_SYS_EVENT_SYSTEM_INIT_SECONDARY, dynamic_handler_2, NULL)); + + esp_sys_event_trigger_dynamic(ESP_SYS_EVENT_SHUTDOWN, NULL); + TEST_ASSERT_EQUAL(1, s_dynamic_handler_call_count); + + esp_sys_event_trigger_dynamic(ESP_SYS_EVENT_SYSTEM_INIT_SECONDARY, NULL); + TEST_ASSERT_EQUAL(2, s_dynamic_handler_call_count); + + esp_sys_event_trigger_dynamic(ESP_SYS_EVENT_SYSTEM_INIT_CORE, NULL); + TEST_ASSERT_EQUAL(2, s_dynamic_handler_call_count); + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_unregister(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_unregister(ESP_SYS_EVENT_SYSTEM_INIT_SECONDARY, dynamic_handler_2, NULL)); +} + +TEST_CASE("esp_sys_event_get_static_handlers - returns valid bounds", "[esp_sys_event]") +{ + esp_sys_event_iter_t iter = esp_sys_event_get_static_handlers(ESP_SYS_EVENT_SHUTDOWN); + + TEST_ASSERT_NOT_NULL(iter.start); + TEST_ASSERT_NOT_NULL(iter.end); + TEST_ASSERT(iter.start <= iter.end); + // The 3 ESP_SHUTDOWN_HANDLER_REGISTER entries above must be present. + TEST_ASSERT_GREATER_OR_EQUAL_size_t((size_t)3, (size_t)(iter.end - iter.start)); + TEST_ASSERT_EQUAL((size_t)3, count_test_shutdown_handlers()); + + iter = esp_sys_event_get_static_handlers(ESP_SYS_EVENT_MAX); + TEST_ASSERT_NULL(iter.start); + TEST_ASSERT_NULL(iter.end); +} + +TEST_CASE("esp_sys_event_trigger - calls both static and dynamic handlers", "[esp_sys_event]") +{ + reset_static_state(); + s_dynamic_handler_call_count = 0; + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_register(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, NULL)); + + esp_sys_event_trigger(ESP_SYS_EVENT_SHUTDOWN, NULL); + + // All 3 static SHUTDOWN handlers fired + TEST_ASSERT_EQUAL(3, s_static_call_count); + // Dynamic handler fired + TEST_ASSERT_EQUAL(1, s_dynamic_handler_call_count); + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_unregister(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, NULL)); +} + +// ============== handler_dump tests ============== + +TEST_CASE("esp_sys_event_handler_dump - invalid id returns error", "[esp_sys_event]") +{ + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_sys_event_handler_dump(ESP_SYS_EVENT_MAX, stdout)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, + esp_sys_event_handler_dump((esp_sys_event_id_t)(ESP_SYS_EVENT_MAX + 1), stdout)); +} + +TEST_CASE("esp_sys_event_handler_dump - null stream skips output", "[esp_sys_event]") +{ + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_handler_dump(ESP_SYS_EVENT_SHUTDOWN, NULL)); +} + +TEST_CASE("esp_sys_event_handler_dump - lists dynamic handlers with correct pointer", "[esp_sys_event]") +{ + char buf[512]; + memset(buf, 0, sizeof(buf)); + FILE *f = fmemopen(buf, sizeof(buf) - 1, "w"); + TEST_ASSERT_NOT_NULL(f); + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_register(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_register(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_2, NULL)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_handler_dump(ESP_SYS_EVENT_SHUTDOWN, f)); + fclose(f); + + ESP_LOGI(TAG, "dump output:\n%s", buf); + + char expected_0[64], expected_1[64]; + snprintf(expected_0, sizeof(expected_0), "%p", (void *)dynamic_handler_1); + snprintf(expected_1, sizeof(expected_1), "%p", (void *)dynamic_handler_2); + bool event_found = strstr(buf, "Event") != NULL; + bool handler_1_found = strstr(buf, expected_0) != NULL; + bool handler_2_found = strstr(buf, expected_1) != NULL; + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_unregister(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_unregister(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_2, NULL)); + + TEST_ASSERT_TRUE(event_found); + TEST_ASSERT_TRUE_MESSAGE(handler_1_found, "dynamic_handler_1 pointer not found in dump"); + TEST_ASSERT_TRUE_MESSAGE(handler_2_found, "dynamic_handler_2 pointer not found in dump"); +} + +TEST_CASE("esp_sys_event_handler_dump - static handlers appear before dynamic", "[esp_sys_event]") +{ + // SHUTDOWN has 3 static handlers registered in this file, so static[0] + // is guaranteed present. + char buf[1024]; + memset(buf, 0, sizeof(buf)); + FILE *f = fmemopen(buf, sizeof(buf) - 1, "w"); + TEST_ASSERT_NOT_NULL(f); + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_register(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, NULL)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_handler_dump(ESP_SYS_EVENT_SHUTDOWN, f)); + fclose(f); + + ESP_LOGI(TAG, "dump output:\n%s", buf); + + const char *static0 = strstr(buf, "static[0]"); + const char *dynamic0 = strstr(buf, "dynamic["); + bool static_before_dynamic = static0 != NULL && dynamic0 != NULL && static0 < dynamic0; + + TEST_ASSERT_EQUAL(ESP_OK, esp_sys_event_unregister(ESP_SYS_EVENT_SHUTDOWN, dynamic_handler_1, NULL)); + + TEST_ASSERT_NOT_NULL_MESSAGE(static0, "static[0] not found in dump"); + TEST_ASSERT_NOT_NULL_MESSAGE(dynamic0, "dynamic handler not found in dump"); + TEST_ASSERT_TRUE_MESSAGE(static_before_dynamic, "static handlers must appear before dynamic handlers"); +} diff --git a/components/esp_system/test_apps/esp_sys_event/pytest_esp_sys_event.py b/components/esp_system/test_apps/esp_sys_event/pytest_esp_sys_event.py new file mode 100644 index 00000000000..a9a57892a49 --- /dev/null +++ b/components/esp_system/test_apps/esp_sys_event/pytest_esp_sys_event.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@idf_parametrize('target', ['supported_targets'], indirect=['target']) +def test_esp_sys_event(dut: Dut) -> None: + dut.run_all_single_board_cases(timeout=60) diff --git a/components/esp_system/test_apps/esp_sys_event/sdkconfig.defaults b/components/esp_system/test_apps/esp_sys_event/sdkconfig.defaults new file mode 100644 index 00000000000..62e54cbdd99 --- /dev/null +++ b/components/esp_system/test_apps/esp_sys_event/sdkconfig.defaults @@ -0,0 +1,2 @@ +# Defaults for esp_sys_event test +CONFIG_ESP_TASK_WDT_INIT=n diff --git a/components/esp_system/test_apps/linux_apis/main/esp_system_test.c b/components/esp_system/test_apps/linux_apis/main/esp_system_test.c index 7e5c912be7e..5b5b4539e15 100644 --- a/components/esp_system/test_apps/linux_apis/main/esp_system_test.c +++ b/components/esp_system/test_apps/linux_apis/main/esp_system_test.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -49,17 +49,15 @@ TEST_CASE("reset_reason", "[esp_system]") TEST_CASE("unregister_handler_works", "[esp_system]") { token = 0; - // for some reason, the handlers are executed in reverse order of adding handlers, so we always - // register the jumping handler at first to make it execute last - TEST_ASSERT_EQUAL(ESP_OK, esp_register_shutdown_handler(jump_back_shutdown_handler)); TEST_ASSERT_EQUAL(ESP_OK, esp_register_shutdown_handler(action)); + TEST_ASSERT_EQUAL(ESP_OK, esp_register_shutdown_handler(jump_back_shutdown_handler)); TEST_ASSERT_EQUAL(ESP_OK, esp_unregister_shutdown_handler(action)); if (setjmp(env) == 0) { esp_restart(); } - // fist unregister before any assert to avoid skipping by assert's longjmp + // first unregister before any assert to avoid skipping by assert's longjmp cleanup(); TEST_ASSERT_EQUAL(0, token); @@ -76,8 +74,8 @@ TEST_CASE("register_shutdown_handler_twice_fails", "[esp_system]") TEST_CASE("register_shutdown_handler_works", "[esp_system]") { token = 0; - TEST_ASSERT_EQUAL(esp_register_shutdown_handler(jump_back_shutdown_handler), ESP_OK); - TEST_ASSERT_EQUAL(esp_register_shutdown_handler(action), ESP_OK); + TEST_ASSERT_EQUAL(ESP_OK, esp_register_shutdown_handler(action)); + TEST_ASSERT_EQUAL(ESP_OK, esp_register_shutdown_handler(jump_back_shutdown_handler)); if (setjmp(env) == 0) { esp_restart(); @@ -88,15 +86,14 @@ TEST_CASE("register_shutdown_handler_works", "[esp_system]") TEST_ASSERT_EQUAL(1, token); } -TEST_CASE("register_too_many_shutdown_handler_fails", "[esp_system]") +TEST_CASE("register_many_shutdown_handlers_succeeds", "[esp_system]") { TEST_ASSERT_EQUAL(ESP_OK, esp_register_shutdown_handler(dummy_shutdown_handler_0)); TEST_ASSERT_EQUAL(ESP_OK, esp_register_shutdown_handler(dummy_shutdown_handler_1)); TEST_ASSERT_EQUAL(ESP_OK, esp_register_shutdown_handler(dummy_shutdown_handler_2)); TEST_ASSERT_EQUAL(ESP_OK, esp_register_shutdown_handler(dummy_shutdown_handler_3)); TEST_ASSERT_EQUAL(ESP_OK, esp_register_shutdown_handler(dummy_shutdown_handler_4)); - - TEST_ASSERT_EQUAL(esp_register_shutdown_handler(jump_back_shutdown_handler), ESP_ERR_NO_MEM); + TEST_ASSERT_EQUAL(ESP_OK, esp_register_shutdown_handler(jump_back_shutdown_handler)); cleanup(); } diff --git a/components/esp_system/test_apps/sys_init_fn/main/test_init_fn_defs.c b/components/esp_system/test_apps/sys_init_fn/main/test_init_fn_defs.c index 9343571584d..c984532e1dc 100644 --- a/components/esp_system/test_apps/sys_init_fn/main/test_init_fn_defs.c +++ b/components/esp_system/test_apps/sys_init_fn/main/test_init_fn_defs.c @@ -8,9 +8,9 @@ * This file defines system init functions whose execution is verified * by the test cases in test_sys_init_fn.c. * - * The functions are placed into the esp_sys_init_fn linker section via the - * system init macro. On Linux this uses ELF section sorting; on macOS - * the same section is resolved at runtime via getsectiondata(). + * The functions are placed into esp_sys_event linker sections via the system + * init macro. On Linux this uses ELF section sorting; on macOS the same + * sections are resolved at runtime via getsectiondata(). * * Important: this file must be compiled into the same binary as the test * runner so the linker/loader sees the section entries. diff --git a/components/esp_timer/src/system_time.c b/components/esp_timer/src/system_time.c index 0e39c392338..b95abcdb510 100644 --- a/components/esp_timer/src/system_time.c +++ b/components/esp_timer/src/system_time.c @@ -13,26 +13,25 @@ #include "esp_timer_impl.h" #include "esp_system.h" #include "esp_newlib.h" -#include "esp_log.h" #include "esp_private/startup_internal.h" #include "esp_rtc_time.h" -ESP_LOG_ATTR_TAG(TAG, "system_time"); - // Correction for underlying timer to keep definition // of system time consistent. static int64_t s_correction_us = 0; +#if defined(CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER) && defined(CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER) +ESP_SHUTDOWN_HANDLER_REGISTER(esp_sync_timekeeping_timers_shutdown, 100) +{ + esp_sync_timekeeping_timers(); + return ESP_OK; +} +#endif + void esp_timer_impl_init_system_time(void) { s_correction_us = esp_rtc_get_time_us() - g_startup_time - esp_timer_impl_get_time(); -#if defined(CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER) && defined(CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER) - esp_err_t err = esp_register_shutdown_handler(esp_sync_timekeeping_timers); - if (err != ESP_OK) { - ESP_LOGW(TAG, "Register shutdown handler failed, err = 0x%x", err); - } -#endif } int64_t ESP_TIMER_IRAM_ATTR esp_system_get_time(void)