Merge branch 'refactor/etm_driver_soc_caps' into 'master'

refactor(etm): clean up soc macros && enhance thread safety

Closes IDF-13634

See merge request espressif/esp-idf!41201
This commit is contained in:
morris
2025-08-21 10:19:19 +08:00
44 changed files with 250 additions and 283 deletions
+3 -3
View File
@@ -5,7 +5,7 @@ idf_build_get_property(non_os_build NON_OS_BUILD)
if(${target} STREQUAL "linux")
idf_component_register(SRCS "port/linux/esp_random.c"
"port/linux/chip_info.c"
INCLUDE_DIRS "include")
INCLUDE_DIRS "include" "etm/include")
return()
endif()
@@ -122,7 +122,7 @@ if(NOT non_os_build)
endif()
if(CONFIG_SOC_ETM_SUPPORTED)
list(APPEND srcs "esp_etm.c")
list(APPEND srcs "etm/esp_etm.c")
endif()
if(CONFIG_SOC_PAU_SUPPORTED)
@@ -175,7 +175,7 @@ else()
endif()
set(public_include_dirs "include" "include/soc"
"dma/include" "ldo/include" "debug_probe/include"
"dma/include" "ldo/include" "debug_probe/include" "etm/include"
"mspi_timing_tuning/include" "mspi_timing_tuning/tuning_scheme_impl/include"
"power_supply/include")
+1 -10
View File
@@ -238,17 +238,8 @@ menu "Hardware Settings"
so that these functions can be IRAM-safe and able to be called in the other IRAM interrupt context.
endmenu
menu "ETM Configuration"
depends on SOC_ETM_SUPPORTED
config ETM_ENABLE_DEBUG_LOG
bool "Enable debug log"
default n
help
whether to enable the debug log message for ETM core driver.
Note that, this option only controls the ETM related driver log, won't affect other drivers.
endmenu # ETM Configuration
rsource "./dma/Kconfig.dma"
rsource "./etm/Kconfig.etm"
menu "Main XTAL Config"
orsource "./port/$IDF_TARGET/Kconfig.xtal"
+12
View File
@@ -0,0 +1,12 @@
menu "Event Task Matrix (ETM) Configurations"
depends on SOC_ETM_SUPPORTED
config ETM_ENABLE_DEBUG_LOG
bool "Force enable debug log"
default n
help
If enabled, ETM driver will:
1. ignore the global logging settings
2. compile all log messages into the binary
3. set the runtime log level to VERBOSE
Please enable this option by caution, as it will increase the binary size.
endmenu # ETM Configuration
@@ -5,17 +5,17 @@
*/
#include <stdlib.h>
#include <stdatomic.h>
#include <sys/cdefs.h>
#include <sys/lock.h>
#include "sdkconfig.h"
#if CONFIG_ETM_ENABLE_DEBUG_LOG
// The local log level must be defined before including esp_log.h
// Set the maximum log level for this source file
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#endif
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "soc/soc_caps.h"
#include "soc/etm_periph.h"
#include "esp_log.h"
#include "esp_check.h"
@@ -39,34 +39,35 @@
#define ETM_RCC_ATOMIC()
#endif
static const char *TAG = "etm";
#define TAG "etm"
typedef struct etm_platform_t etm_platform_t;
typedef struct etm_group_t etm_group_t;
typedef struct esp_etm_channel_t esp_etm_channel_t;
struct etm_platform_t {
_lock_t mutex; // platform level mutex lock
etm_group_t *groups[SOC_ETM_GROUPS]; // etm group pool
int group_ref_counts[SOC_ETM_GROUPS]; // reference count used to protect group install/uninstall
_lock_t mutex; // platform level mutex lock
etm_group_t *groups[SOC_ETM_ATTR(INST_NUM)]; // etm group pool
int group_ref_counts[SOC_ETM_ATTR(INST_NUM)]; // reference count used to protect group install/uninstall
};
struct etm_group_t {
int group_id; // hardware group id
etm_hal_context_t hal; // hardware abstraction layer context
portMUX_TYPE spinlock; // to protect per-group register level concurrent access
esp_etm_channel_t *chans[SOC_ETM_CHANNELS_PER_GROUP];
portMUX_TYPE spinlock; // to protect per-group light weight resource access
esp_etm_channel_t *chans[SOC_ETM_ATTR(CHANS_PER_INST)]; // array of channels in the group
};
typedef enum {
ETM_CHAN_FSM_INIT,
ETM_CHAN_FSM_ENABLE,
ETM_CHAN_FSM_WAIT,
} etm_chan_fsm_t;
struct esp_etm_channel_t {
int chan_id; // Channel ID
etm_group_t *group; // which group this channel belongs to
etm_chan_fsm_t fsm; // record ETM channel's driver state
_Atomic etm_chan_fsm_t fsm; // record ETM channel's driver state
esp_etm_event_handle_t event; // which event is connect to the channel
esp_etm_task_handle_t task; // which task is connect to the channel
};
@@ -79,21 +80,21 @@ static esp_err_t etm_create_sleep_retention_link_cb(void *arg)
{
etm_group_t *group = (etm_group_t *)arg;
int group_id = group->group_id;
esp_err_t err = sleep_retention_entries_create(etm_reg_retention_info[group_id].regdma_entry_array,
etm_reg_retention_info[group_id].array_size,
REGDMA_LINK_PRI_ETM, etm_reg_retention_info[group_id].module);
esp_err_t err = sleep_retention_entries_create(soc_etm_retention_info[group_id].regdma_entry_array,
soc_etm_retention_info[group_id].array_size,
REGDMA_LINK_PRI_ETM, soc_etm_retention_info[group_id].module);
return err;
}
static void etm_create_retention_module(etm_group_t *group)
{
int group_id = group->group_id;
sleep_retention_module_t module = etm_reg_retention_info[group_id].module;
sleep_retention_module_t module = soc_etm_retention_info[group_id].module;
_lock_acquire(&s_platform.mutex);
if (sleep_retention_is_module_inited(module) && !sleep_retention_is_module_created(module)) {
if (sleep_retention_module_allocate(module) != ESP_OK) {
// even though the sleep retention module create failed, ETM driver should still work, so just warning here
ESP_LOGW(TAG, "create retention link failed %d, power domain won't be turned off during sleep", group_id);
ESP_LOGW(TAG, "create retention link failed on ETM Group%d, power domain won't be turned off during sleep", group_id);
}
}
_lock_release(&s_platform.mutex);
@@ -120,8 +121,9 @@ static etm_group_t *etm_acquire_group_handle(int group_id)
etm_ll_enable_bus_clock(group_id, true);
etm_ll_reset_register(group_id);
}
#if ETM_USE_RETENTION_LINK
sleep_retention_module_t module = etm_reg_retention_info[group_id].module;
sleep_retention_module_t module = soc_etm_retention_info[group_id].module;
sleep_retention_module_init_param_t init_param = {
.cbs = {
.create = {
@@ -131,11 +133,13 @@ static etm_group_t *etm_acquire_group_handle(int group_id)
},
.depends = RETENTION_MODULE_BITMAP_INIT(CLOCK_SYSTEM)
};
// retention module init must be called BEFORE the hal init
if (sleep_retention_module_init(module, &init_param) != ESP_OK) {
// even though the sleep retention module init failed, ETM driver may still work, so just warning here
ESP_LOGW(TAG, "init sleep retention failed %d, power domain may be turned off during sleep", group_id);
ESP_LOGW(TAG, "init sleep retention failed on ETM Group%d, power domain may be turned off during sleep", group_id);
}
#endif // ETM_USE_RETENTION_LINK
// initialize HAL context
etm_hal_init(&group->hal);
}
@@ -166,23 +170,25 @@ static void etm_release_group_handle(etm_group_t *group)
assert(s_platform.groups[group_id]);
do_deinitialize = true;
s_platform.groups[group_id] = NULL; // deregister from platform
etm_hal_deinit(&group->hal);
// disable the bus clock for the ETM registers
ETM_RCC_ATOMIC() {
etm_ll_enable_bus_clock(group_id, false);
}
}
_lock_release(&s_platform.mutex);
if (do_deinitialize) {
#if ETM_USE_RETENTION_LINK
sleep_retention_module_t module = etm_reg_retention_info[group_id].module;
sleep_retention_module_t module = soc_etm_retention_info[group_id].module;
if (sleep_retention_is_module_created(module)) {
sleep_retention_module_free(module);
}
if (sleep_retention_is_module_inited(module)) {
sleep_retention_module_deinit(module);
}
#endif
#endif // ETM_USE_RETENTION_LINK
}
_lock_release(&s_platform.mutex);
if (do_deinitialize) {
free(group);
ESP_LOGD(TAG, "del group (%d)", group_id);
}
@@ -192,25 +198,24 @@ static esp_err_t etm_chan_register_to_group(esp_etm_channel_t *chan)
{
etm_group_t *group = NULL;
int chan_id = -1;
for (int i = 0; i < SOC_ETM_GROUPS; i++) {
for (int i = 0; i < SOC_ETM_ATTR(INST_NUM); i++) {
group = etm_acquire_group_handle(i);
ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", i);
// loop to search free channel in the group
esp_os_enter_critical(&group->spinlock);
for (int j = 0; j < SOC_ETM_CHANNELS_PER_GROUP; j++) {
for (int j = 0; j < SOC_ETM_ATTR(CHANS_PER_INST); j++) {
if (!group->chans[j]) {
chan_id = j;
group->chans[j] = chan;
chan->chan_id = chan_id;
chan->group = group;
break;
}
}
esp_os_exit_critical(&group->spinlock);
if (chan_id < 0) {
etm_release_group_handle(group);
group = NULL;
} else {
chan->chan_id = chan_id;
chan->group = group;
break;
}
}
@@ -240,31 +245,34 @@ static esp_err_t etm_chan_destroy(esp_etm_channel_t *chan)
esp_err_t esp_etm_new_channel(const esp_etm_channel_config_t *config, esp_etm_channel_handle_t *ret_chan)
{
#if CONFIG_ETM_ENABLE_DEBUG_LOG
esp_log_level_set(TAG, ESP_LOG_DEBUG);
#endif
esp_err_t ret = ESP_OK;
esp_etm_channel_t *chan = NULL;
ESP_GOTO_ON_FALSE(config && ret_chan, ESP_ERR_INVALID_ARG, err, TAG, "invalid args");
ESP_RETURN_ON_FALSE(config && ret_chan, ESP_ERR_INVALID_ARG, TAG, "invalid args");
[[maybe_unused]] bool allow_pd = config->flags.allow_pd == 1;
#if !SOC_ETM_SUPPORT_SLEEP_RETENTION
ESP_RETURN_ON_FALSE(config->flags.allow_pd == 0, ESP_ERR_NOT_SUPPORTED, TAG, "not able to power down in light sleep");
ESP_RETURN_ON_FALSE(allow_pd == 0, ESP_ERR_NOT_SUPPORTED, TAG, "not able to power down in light sleep");
#endif // SOC_ETM_SUPPORT_SLEEP_RETENTION
chan = heap_caps_calloc(1, sizeof(esp_etm_channel_t), ETM_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(chan, ESP_ERR_NO_MEM, err, TAG, "no mem for channel");
// allocate channel memory from internal memory because it contains atomic variable
chan = heap_caps_calloc(1, sizeof(esp_etm_channel_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_RETURN_ON_FALSE(chan, ESP_ERR_NO_MEM, TAG, "no mem for channel");
// register channel to the group, one group can have multiple channels
ESP_GOTO_ON_ERROR(etm_chan_register_to_group(chan), err, TAG, "register channel failed");
ESP_GOTO_ON_ERROR(etm_chan_register_to_group(chan), err, TAG, "register channel to group failed");
etm_group_t *group = chan->group;
int group_id = group->group_id;
int chan_id = chan->chan_id;
// set the initial state to INIT
atomic_init(&chan->fsm, ETM_CHAN_FSM_INIT);
#if ETM_USE_RETENTION_LINK
if (config->flags.allow_pd != 0) {
if (allow_pd) {
etm_create_retention_module(group);
}
#endif // ETM_USE_RETENTION_LINK
chan->fsm = ETM_CHAN_FSM_INIT;
ESP_LOGD(TAG, "new etm channel (%d,%d) at %p", group_id, chan_id, chan);
*ret_chan = chan;
return ESP_OK;
@@ -279,7 +287,9 @@ err:
esp_err_t esp_etm_del_channel(esp_etm_channel_handle_t chan)
{
ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid args");
ESP_RETURN_ON_FALSE(chan->fsm == ETM_CHAN_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "channel is not in init state");
etm_chan_fsm_t expected_fsm = ETM_CHAN_FSM_INIT;
ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&chan->fsm, &expected_fsm, ETM_CHAN_FSM_WAIT),
ESP_ERR_INVALID_STATE, TAG, "channel not in init state");
etm_group_t *group = chan->group;
int group_id = group->group_id;
int chan_id = chan->chan_id;
@@ -296,27 +306,63 @@ esp_err_t esp_etm_del_channel(esp_etm_channel_handle_t chan)
esp_err_t esp_etm_channel_enable(esp_etm_channel_handle_t chan)
{
ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(chan->fsm == ETM_CHAN_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "channel is not in init state");
etm_group_t *group = chan->group;
etm_ll_enable_channel(group->hal.regs, chan->chan_id);
chan->fsm = ETM_CHAN_FSM_ENABLE;
return ESP_OK;
if (!chan) {
return ESP_ERR_INVALID_ARG;
}
etm_chan_fsm_t expected_fsm = ETM_CHAN_FSM_INIT;
if (atomic_compare_exchange_strong(&chan->fsm, &expected_fsm, ETM_CHAN_FSM_WAIT)) {
etm_group_t *group = chan->group;
// no race condition here even without a lock, because the underlying register is write only and each channel has its own position
etm_ll_enable_channel(group->hal.regs, chan->chan_id);
// change state to ENABLE
atomic_store(&chan->fsm, ETM_CHAN_FSM_ENABLE);
return ESP_OK;
} else {
return ESP_ERR_INVALID_STATE;
}
}
esp_err_t esp_etm_channel_disable(esp_etm_channel_handle_t chan)
{
ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(chan->fsm == ETM_CHAN_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "channel not in enable state");
etm_group_t *group = chan->group;
etm_ll_disable_channel(group->hal.regs, chan->chan_id);
chan->fsm = ETM_CHAN_FSM_INIT;
return ESP_OK;
if (!chan) {
return ESP_ERR_INVALID_ARG;
}
etm_chan_fsm_t expected_fsm = ETM_CHAN_FSM_ENABLE;
if (atomic_compare_exchange_strong(&chan->fsm, &expected_fsm, ETM_CHAN_FSM_WAIT)) {
etm_group_t *group = chan->group;
// no race condition here even without a lock, because the underlying register is write only and each channel has its own position
etm_ll_disable_channel(group->hal.regs, chan->chan_id);
// change state to INIT
atomic_store(&chan->fsm, ETM_CHAN_FSM_INIT);
return ESP_OK;
} else {
return ESP_ERR_INVALID_STATE;
}
}
esp_err_t esp_etm_channel_connect(esp_etm_channel_handle_t chan, esp_etm_event_handle_t event, esp_etm_task_handle_t task)
{
ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
if (!chan) {
return ESP_ERR_INVALID_ARG;
}
bool valid_state = false;
etm_chan_fsm_t expected_fsm = ETM_CHAN_FSM_INIT;
etm_chan_fsm_t restore_fsm = ETM_CHAN_FSM_INIT;
// this function can be called only when the channel is in init or enable state
if (atomic_compare_exchange_strong(&chan->fsm, &expected_fsm, ETM_CHAN_FSM_WAIT)) {
valid_state = true;
restore_fsm = ETM_CHAN_FSM_INIT;
} else {
expected_fsm = ETM_CHAN_FSM_ENABLE;
if (atomic_compare_exchange_strong(&chan->fsm, &expected_fsm, ETM_CHAN_FSM_WAIT)) {
valid_state = true;
restore_fsm = ETM_CHAN_FSM_ENABLE;
}
}
if (!valid_state) {
return ESP_ERR_INVALID_STATE;
}
etm_group_t *group = chan->group;
uint32_t event_id = 0;
uint32_t task_id = 0;
@@ -332,6 +378,8 @@ esp_err_t esp_etm_channel_connect(esp_etm_channel_handle_t chan, esp_etm_event_h
etm_ll_channel_set_task(group->hal.regs, chan->chan_id, task_id);
chan->event = event;
chan->task = task;
// restore the state
atomic_store(&chan->fsm, restore_fsm);
ESP_LOGD(TAG, "event %"PRIu32" => channel %d", event_id, chan->chan_id);
ESP_LOGD(TAG, "channel %d => task %"PRIu32, chan->chan_id, task_id);
return ESP_OK;
@@ -357,11 +405,11 @@ esp_err_t esp_etm_dump(FILE *out_stream)
fprintf(out_stream, "===========ETM Dump Start==========\r\n");
char line[80];
size_t len = sizeof(line);
for (int i = 0; i < SOC_ETM_GROUPS; i++) {
for (int i = 0; i < SOC_ETM_ATTR(INST_NUM); i++) {
group = etm_acquire_group_handle(i);
ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", i);
etm_hal_context_t *hal = &group->hal;
for (int j = 0; j < SOC_ETM_CHANNELS_PER_GROUP; j++) {
for (int j = 0; j < SOC_ETM_ATTR(CHANS_PER_INST); j++) {
bool print_line = true;
esp_os_enter_critical(&group->spinlock);
etm_chan = group->chans[j];
@@ -394,3 +442,11 @@ esp_err_t esp_etm_dump(FILE *out_stream)
fprintf(out_stream, "===========ETM Dump End============\r\n");
return ESP_OK;
}
#if CONFIG_ETM_ENABLE_DEBUG_LOG
__attribute__((constructor))
static void etm_override_default_log_level(void)
{
esp_log_level_set(TAG, ESP_LOG_VERBOSE);
}
#endif
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -10,16 +10,15 @@
#include "freertos/task.h"
#include "unity.h"
#include "esp_etm.h"
#include "soc/soc_caps.h"
#include "esp_attr.h"
#include "soc/etm_periph.h"
TEST_CASE("etm_channel_install_uninstall", "[etm]")
{
printf("install etm channels exhaustively\r\n");
esp_etm_channel_handle_t etm_chans[SOC_ETM_GROUPS][SOC_ETM_CHANNELS_PER_GROUP];
esp_etm_channel_handle_t etm_chans[SOC_ETM_ATTR(INST_NUM)][SOC_ETM_ATTR(CHANS_PER_INST)];
esp_etm_channel_config_t config = {};
for (int i = 0; i < SOC_ETM_GROUPS; i++) {
for (int j = 0; j < SOC_ETM_CHANNELS_PER_GROUP; j++) {
for (int i = 0; i < SOC_ETM_ATTR(INST_NUM); i++) {
for (int j = 0; j < SOC_ETM_ATTR(CHANS_PER_INST); j++) {
TEST_ESP_OK(esp_etm_new_channel(&config, &etm_chans[i][j]));
}
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, esp_etm_new_channel(&config, &etm_chans[0][0]));
@@ -31,8 +30,8 @@ TEST_CASE("etm_channel_install_uninstall", "[etm]")
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, esp_etm_del_channel(etm_chans[0][0]));
TEST_ESP_OK(esp_etm_channel_disable(etm_chans[0][0]));
for (int i = 0; i < SOC_ETM_GROUPS; i++) {
for (int j = 0; j < SOC_ETM_CHANNELS_PER_GROUP; j++) {
for (int i = 0; i < SOC_ETM_ATTR(INST_NUM); i++) {
for (int j = 0; j < SOC_ETM_ATTR(CHANS_PER_INST); j++) {
TEST_ESP_OK(esp_etm_del_channel(etm_chans[i][j]));
}
}