Files
2026-07-13 13:04:25 +08:00

235 lines
7.5 KiB
C

/*
* 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 <stdio.h>
#include <stdatomic.h>
#include <stdlib.h>
// 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_<ID> 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;
}