From fb26d0e11f09d46fb75fbcc2b167096d86094732 Mon Sep 17 00:00:00 2001 From: morris Date: Tue, 12 Jul 2022 14:44:10 +0800 Subject: [PATCH] etm: added etm channel allocator --- .../esp_hw_support/.build-test-rules.yml | 4 + components/esp_hw_support/CMakeLists.txt | 4 + components/esp_hw_support/Kconfig | 10 + components/esp_hw_support/esp_etm.c | 321 ++++++++++++++++++ components/esp_hw_support/include/esp_etm.h | 147 ++++++++ .../include/esp_private/etm_interface.h | 71 ++++ .../test_apps/etm/CMakeLists.txt | 5 + .../esp_hw_support/test_apps/etm/README.md | 2 + .../test_apps/etm/main/CMakeLists.txt | 9 + .../test_apps/etm/main/test_app_main.c | 60 ++++ .../test_apps/etm/main/test_etm_core.c | 39 +++ .../test_apps/etm/pytest_etm.py | 20 ++ .../test_apps/etm/sdkconfig.ci.release | 5 + .../test_apps/etm/sdkconfig.defaults | 2 + components/hal/CMakeLists.txt | 4 + .../hal/esp32c6/include/hal/clk_gate_ll.h | 8 + components/hal/esp32c6/include/hal/etm_ll.h | 103 ++++++ components/hal/etm_hal.c | 19 ++ components/hal/include/hal/etm_hal.h | 46 +++ .../esp32c6/include/soc/Kconfig.soc_caps.in | 12 + .../soc/esp32c6/include/soc/periph_defs.h | 1 + components/soc/esp32c6/include/soc/soc_caps.h | 7 +- 22 files changed, 898 insertions(+), 1 deletion(-) create mode 100644 components/esp_hw_support/esp_etm.c create mode 100644 components/esp_hw_support/include/esp_etm.h create mode 100644 components/esp_hw_support/include/esp_private/etm_interface.h create mode 100644 components/esp_hw_support/test_apps/etm/CMakeLists.txt create mode 100644 components/esp_hw_support/test_apps/etm/README.md create mode 100644 components/esp_hw_support/test_apps/etm/main/CMakeLists.txt create mode 100644 components/esp_hw_support/test_apps/etm/main/test_app_main.c create mode 100644 components/esp_hw_support/test_apps/etm/main/test_etm_core.c create mode 100644 components/esp_hw_support/test_apps/etm/pytest_etm.py create mode 100644 components/esp_hw_support/test_apps/etm/sdkconfig.ci.release create mode 100644 components/esp_hw_support/test_apps/etm/sdkconfig.defaults create mode 100644 components/hal/esp32c6/include/hal/etm_ll.h create mode 100644 components/hal/etm_hal.c create mode 100644 components/hal/include/hal/etm_hal.h diff --git a/components/esp_hw_support/.build-test-rules.yml b/components/esp_hw_support/.build-test-rules.yml index 01b0ede9a7..e61b946b79 100644 --- a/components/esp_hw_support/.build-test-rules.yml +++ b/components/esp_hw_support/.build-test-rules.yml @@ -5,3 +5,7 @@ components/esp_hw_support/test_apps/dma: - if: IDF_TARGET in ["esp32"] temporary: false reason: Neither GDMA nor CPDMA is supported on ESP32 + +components/esp_hw_support/test_apps/etm: + disable: + - if: SOC_ETM_SUPPORTED != 1 diff --git a/components/esp_hw_support/CMakeLists.txt b/components/esp_hw_support/CMakeLists.txt index 08053a828d..ceab3d741a 100644 --- a/components/esp_hw_support/CMakeLists.txt +++ b/components/esp_hw_support/CMakeLists.txt @@ -49,6 +49,10 @@ if(NOT BOOTLOADER_BUILD) list(APPEND srcs "esp_hmac.c") endif() + if(CONFIG_SOC_ETM_SUPPORTED) + list(APPEND srcs "esp_etm.c") + endif() + # ESP32C6-TODO if(CONFIG_IDF_TARGET_ESP32C6) list(REMOVE_ITEM srcs diff --git a/components/esp_hw_support/Kconfig b/components/esp_hw_support/Kconfig index 989849b0ae..7d022f50c5 100644 --- a/components/esp_hw_support/Kconfig +++ b/components/esp_hw_support/Kconfig @@ -161,6 +161,16 @@ 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 + Wether 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 + menu "MMU Config" # This Config is used for configure the MMU. # Be configured based on flash size selection. diff --git a/components/esp_hw_support/esp_etm.c b/components/esp_hw_support/esp_etm.c new file mode 100644 index 0000000000..ff7261eb73 --- /dev/null +++ b/components/esp_hw_support/esp_etm.c @@ -0,0 +1,321 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#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 +#endif +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "soc/soc_caps.h" +#include "soc/periph_defs.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "esp_etm.h" +#include "hal/etm_hal.h" +#include "hal/etm_ll.h" +#include "esp_private/periph_ctrl.h" +#include "esp_private/etm_interface.h" + +#define ETM_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT + +static const char *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 +}; + +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]; +}; + +typedef enum { + ETM_CHAN_FSM_INIT, + ETM_CHAN_FSM_ENABLE, +} 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 + 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 +}; + +// ETM driver platform, it's always a singleton +static etm_platform_t s_platform; + +static etm_group_t *etm_acquire_group_handle(int group_id) +{ + bool new_group = false; + etm_group_t *group = NULL; + + // prevent install ETM group concurrently + _lock_acquire(&s_platform.mutex); + if (!s_platform.groups[group_id]) { + group = heap_caps_calloc(1, sizeof(etm_group_t), ETM_MEM_ALLOC_CAPS); + if (group) { + new_group = true; + s_platform.groups[group_id] = group; // register to platform + // initialize ETM group members + group->group_id = group_id; + group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; + // enable APB access ETM registers + // if we have multiple ETM groups/instances, we assume the peripheral defines are continuous + periph_module_enable(PERIPH_ETM_MODULE + group_id); + periph_module_reset(PERIPH_ETM_MODULE + group_id); + // initialize HAL context + etm_hal_init(&group->hal); + } + } else { + group = s_platform.groups[group_id]; + } + if (group) { + // someone acquired the group handle means we have a new object that refer to this group + s_platform.group_ref_counts[group_id]++; + } + _lock_release(&s_platform.mutex); + + if (new_group) { + ESP_LOGD(TAG, "new group (%d) at %p", group_id, group); + } + + return group; +} + +static void etm_release_group_handle(etm_group_t *group) +{ + int group_id = group->group_id; + bool do_deinitialize = false; + + _lock_acquire(&s_platform.mutex); + s_platform.group_ref_counts[group_id]--; + if (s_platform.group_ref_counts[group_id] == 0) { + assert(s_platform.groups[group_id]); + do_deinitialize = true; + s_platform.groups[group_id] = NULL; // deregister from platform + periph_module_disable(PERIPH_ETM_MODULE + group_id); + } + _lock_release(&s_platform.mutex); + + if (do_deinitialize) { + free(group); + ESP_LOGD(TAG, "del group (%d)", group_id); + } +} + +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++) { + 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 + portENTER_CRITICAL(&group->spinlock); + for (int j = 0; j < SOC_ETM_CHANNELS_PER_GROUP; j++) { + if (!group->chans[j]) { + chan_id = j; + group->chans[j] = chan; + break; + } + } + portEXIT_CRITICAL(&group->spinlock); + if (chan_id < 0) { + etm_release_group_handle(group); + group = NULL; + } else { + chan->chan_id = chan_id; + chan->group = group; + break;; + } + } + ESP_RETURN_ON_FALSE(chan_id != -1, ESP_ERR_NOT_FOUND, TAG, "no free channel"); + return ESP_OK; +} + +static void etm_chan_unregister_from_group(esp_etm_channel_t *chan) +{ + etm_group_t *group = chan->group; + int chan_id = chan->chan_id; + portENTER_CRITICAL(&group->spinlock); + group->chans[chan_id] = NULL; + portEXIT_CRITICAL(&group->spinlock); + // channel has a reference on group, release it now + etm_release_group_handle(group); +} + +static esp_err_t etm_chan_destroy(esp_etm_channel_t *chan) +{ + if (chan->group) { + etm_chan_unregister_from_group(chan); + } + free(chan); + return ESP_OK; +} + +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"); + + 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"); + // 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"); + etm_group_t *group = chan->group; + int group_id = group->group_id; + int chan_id = chan->chan_id; + + 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; + +err: + if (chan) { + etm_chan_destroy(chan); + } + return ret; +} + +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_group_t *group = chan->group; + int group_id = group->group_id; + int chan_id = chan->chan_id; + + // disconnect the channel from any event or task + etm_ll_channel_set_event(group->hal.regs, chan_id, 0); + etm_ll_channel_set_task(group->hal.regs, chan_id, 0); + + ESP_LOGD(TAG, "del etm channel (%d,%d)", group_id, chan_id); + // recycle memory resource + ESP_RETURN_ON_ERROR(etm_chan_destroy(chan), TAG, "destroy etm channel failed"); + return ESP_OK; +} + +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; +} + +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; +} + +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"); + etm_group_t *group = chan->group; + uint32_t event_id = 0; + uint32_t task_id = 0; + + // if the event/task is NULL, then the channel will disconnect from the event/task + if (event) { + event_id = event->event_id; + } + if (task) { + task_id = task->task_id; + } + etm_ll_channel_set_event(group->hal.regs, chan->chan_id, event_id); + etm_ll_channel_set_task(group->hal.regs, chan->chan_id, task_id); + chan->event = event; + chan->task = task; + 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; +} + +esp_err_t esp_etm_del_event(esp_etm_event_handle_t event) +{ + ESP_RETURN_ON_FALSE(event, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return event->del(event); +} + +esp_err_t esp_etm_del_task(esp_etm_task_handle_t task) +{ + ESP_RETURN_ON_FALSE(task, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return task->del(task); +} + +esp_err_t esp_etm_dump(FILE *out_stream) +{ + etm_group_t *group = NULL; + esp_etm_channel_handle_t etm_chan = NULL; + ESP_RETURN_ON_FALSE(out_stream, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + 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++) { + 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++) { + bool print_line = true; + portENTER_CRITICAL(&group->spinlock); + etm_chan = group->chans[j]; + if (etm_ll_is_channel_enabled(hal->regs, j)) { + if (!etm_chan) { + // in case the etm driver is bypassed and some channel is enabled in another way (e.g. by hal driver) + snprintf(line, len, "channel %d is enabled but not recorded\r\n", j); + } else { + // print which event and task the channel is connected to + snprintf(line, len, "channel %d: event %"PRIu32" ==> task %"PRIu32"\r\n", j, + etm_chan->event ? etm_chan->event->event_id : 0, + etm_chan->task ? etm_chan->task->task_id : 0); + } + } else { + if (etm_chan) { + // channel is created, but not enabled by `esp_etm_channel_enable` yet + snprintf(line, len, "channel %d is created but not enabled\r\n", j); + } else { + // a free channel, don't print anything + print_line = false; + } + } + portEXIT_CRITICAL(&group->spinlock); + if (print_line) { + fputs(line, out_stream); + } + } + etm_release_group_handle(group); + } + fprintf(out_stream, "===========ETM Dump End============\r\n"); + return ESP_OK; +} diff --git a/components/esp_hw_support/include/esp_etm.h b/components/esp_hw_support/include/esp_etm.h new file mode 100644 index 0000000000..c0757abf8a --- /dev/null +++ b/components/esp_hw_support/include/esp_etm.h @@ -0,0 +1,147 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief ETM channel handle + */ +typedef struct esp_etm_channel_t *esp_etm_channel_handle_t; + +/** + * @brief ETM event handle + */ +typedef struct esp_etm_event_t *esp_etm_event_handle_t; + +/** + * @brief ETM task handle + */ +typedef struct esp_etm_task_t *esp_etm_task_handle_t; + +/** + * @brief ETM channel configuration + */ +typedef struct { + +} esp_etm_channel_config_t; + +/** + * @brief Allocate an ETM channel + * + * @note The channel can later be freed by `esp_etm_del_channel` + * + * @param[in] config ETM channel configuration + * @param[out] ret_chan Returned ETM channel handle + * @return + * - ESP_OK: Allocate ETM channel successfully + * - ESP_ERR_INVALID_ARG: Allocate ETM channel failed because of invalid argument + * - ESP_ERR_NO_MEM: Allocate ETM channel failed because of out of memory + * - ESP_ERR_NOT_FOUND: Allocate ETM channel failed because all channels are used up and no more free one + * - ESP_FAIL: Allocate ETM channel failed because of other reasons + */ +esp_err_t esp_etm_new_channel(const esp_etm_channel_config_t *config, esp_etm_channel_handle_t *ret_chan); + +/** + * @brief Delete an ETM channel + * + * @param[in] chan ETM channel handle that created by `esp_etm_new_channel` + * @return + * - ESP_OK: Delete ETM channel successfully + * - ESP_ERR_INVALID_ARG: Delete ETM channel failed because of invalid argument + * - ESP_FAIL: Delete ETM channel failed because of other reasons + */ +esp_err_t esp_etm_del_channel(esp_etm_channel_handle_t chan); + +/** + * @brief Enable ETM channel + * + * @note This function will transit the channel state from init to enable. + * + * @param[in] chan ETM channel handle that created by `esp_etm_new_channel` + * @return + * - ESP_OK: Enable ETM channel successfully + * - ESP_ERR_INVALID_ARG: Enable ETM channel failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Enable ETM channel failed because the channel has been enabled already + * - ESP_FAIL: Enable ETM channel failed because of other reasons + */ +esp_err_t esp_etm_channel_enable(esp_etm_channel_handle_t chan); + +/** + * @brief Disable ETM channel + * + * @note This function will transit the channel state from enable to init. + * + * @param[in] chan ETM channel handle that created by `esp_etm_new_channel` + * @return + * - ESP_OK: Disable ETM channel successfully + * - ESP_ERR_INVALID_ARG: Disable ETM channel failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Disable ETM channel failed because the channel is not enabled yet + * - ESP_FAIL: Disable ETM channel failed because of other reasons + */ +esp_err_t esp_etm_channel_disable(esp_etm_channel_handle_t chan); + +/** + * @brief Connect an ETM event to an ETM task via a previously allocated ETM channel + * + * @note Setting the ETM event/task handle to NULL means to disconnect the channel from any event/task + * + * @param[in] chan ETM channel handle that created by `esp_etm_new_channel` + * @param[in] event ETM event handle obtained from a driver/peripheral, e.g. `xxx_new_etm_event` + * @param[in] task ETM task handle obtained from a driver/peripheral, e.g. `xxx_new_etm_task` + * @return + * - ESP_OK: Connect ETM event and task to the channel successfully + * - ESP_ERR_INVALID_ARG: Connect ETM event and task to the channel failed because of invalid argument + * - ESP_FAIL: Connect ETM event and task to the channel failed because of other reasons + */ +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); + +/** + * @brief Delete ETM event + * + * @note Although the ETM event comes from various peripherals, we provide the same user API to delete the event handle seamlessly. + * + * @param[in] event ETM event handle obtained from a driver/peripheral, e.g. `xxx_new_etm_event` + * @return + * - ESP_OK: Delete ETM event successfully + * - ESP_ERR_INVALID_ARG: Delete ETM event failed because of invalid argument + * - ESP_FAIL: Delete ETM event failed because of other reasons + */ +esp_err_t esp_etm_del_event(esp_etm_event_handle_t event); + +/** + * @brief Delete ETM task + * + * @note Although the ETM task comes from various peripherals, we provide the same user API to delete the task handle seamlessly. + * + * @param[in] task ETM task handle obtained from a driver/peripheral, e.g. `xxx_new_etm_task` + * @return + * - ESP_OK: Delete ETM task successfully + * - ESP_ERR_INVALID_ARG: Delete ETM task failed because of invalid argument + * - ESP_FAIL: Delete ETM task failed because of other reasons + */ +esp_err_t esp_etm_del_task(esp_etm_task_handle_t task); + +/** + * @brief Dump ETM channel usages to the given IO stream + * + * @param[in] out_stream IO stream (e.g. stdout) + * @return + * - ESP_OK: Dump ETM channel usages successfully + * - ESP_ERR_INVALID_ARG: Dump ETM channel usages failed because of invalid argument + * - ESP_FAIL: Dump ETM channel usages failed because of other reasons + */ +esp_err_t esp_etm_dump(FILE *out_stream); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hw_support/include/esp_private/etm_interface.h b/components/esp_hw_support/include/esp_private/etm_interface.h new file mode 100644 index 0000000000..860d894760 --- /dev/null +++ b/components/esp_hw_support/include/esp_private/etm_interface.h @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct esp_etm_event_t esp_etm_event_t; +typedef struct esp_etm_task_t esp_etm_task_t; + +/** + * @brief List the peripherals that can trigger ETM task/event + */ +typedef enum { + ETM_TRIG_PERIPH_GPIO, /*!< ETM trigger source: GPIO */ + ETM_TRIG_PERIPH_GDMA, /*!< ETM trigger source: GDMA */ + ETM_TRIG_PERIPH_GPTIMER, /*!< ETM trigger source: GPTimer */ + ETM_TRIG_PERIPH_SYSTIMER, /*!< ETM trigger source: Systimer */ +} etm_trigger_peripheral_t; + +/** + * @brief ETM event interface definition + */ +struct esp_etm_event_t { + /** + * @brief Unique event ID + */ + uint32_t event_id; + + /** + * @brief ETM trigger peripheral + */ + etm_trigger_peripheral_t trig_periph; + + /** + * @brief Resource destroy + */ + esp_err_t (*del)(esp_etm_event_t *event); +}; + +/** + * @brief ETM task interface definition + */ +struct esp_etm_task_t { + /** + * @brief Unique task ID + */ + uint32_t task_id; + + /** + * @brief ETM trigger peripheral + */ + etm_trigger_peripheral_t trig_periph; + + /** + * @brief Resource destroy + */ + esp_err_t (*del)(esp_etm_task_t *task); +}; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hw_support/test_apps/etm/CMakeLists.txt b/components/esp_hw_support/test_apps/etm/CMakeLists.txt new file mode 100644 index 0000000000..d5c75a52b3 --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/CMakeLists.txt @@ -0,0 +1,5 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(etm_test) diff --git a/components/esp_hw_support/test_apps/etm/README.md b/components/esp_hw_support/test_apps/etm/README.md new file mode 100644 index 0000000000..e3ba9c6759 --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-C6 | +| ----------------- | -------- | diff --git a/components/esp_hw_support/test_apps/etm/main/CMakeLists.txt b/components/esp_hw_support/test_apps/etm/main/CMakeLists.txt new file mode 100644 index 0000000000..be94405b35 --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/main/CMakeLists.txt @@ -0,0 +1,9 @@ +set(srcs "test_app_main.c" + "test_etm_core.c") + + + +# In order for the cases defined by `TEST_CASE` to be linked into the final elf, +# the component can be registered as WHOLE_ARCHIVE +idf_component_register(SRCS ${srcs} + WHOLE_ARCHIVE) diff --git a/components/esp_hw_support/test_apps/etm/main/test_app_main.c b/components/esp_hw_support/test_apps/etm/main/test_app_main.c new file mode 100644 index 0000000000..be7beff893 --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/main/test_app_main.c @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2022 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 "esp_newlib.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +// Some resources are lazy allocated in pulse_cnt driver, the threshold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (-300) + +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 %u bytes free, After %u bytes free (delta %d)\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) +{ + /* some FreeRTOS stuff is cleaned up by idle task */ + vTaskDelay(5); + + /* clean up some of the newlib's lazy allocations */ + esp_reent_cleanup(); + + 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(" _____ _____ __ __ _____ _\r\n"); + printf("| ____|_ _| \\/ | |_ _|__ ___| |_\r\n"); + printf("| _| | | | |\\/| | | |/ _ \\/ __| __|\r\n"); + printf("| |___ | | | | | | | | __/\\__ \\ |_\r\n"); + printf("|_____| |_| |_| |_| |_|\\___||___/\\__|\r\n"); + unity_run_menu(); +} diff --git a/components/esp_hw_support/test_apps/etm/main/test_etm_core.c b/components/esp_hw_support/test_apps/etm/main/test_etm_core.c new file mode 100644 index 0000000000..4c2f850efd --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/main/test_etm_core.c @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "esp_etm.h" +#include "soc/soc_caps.h" +#include "esp_attr.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_config_t config = {}; + for (int i = 0; i < SOC_ETM_GROUPS; i++) { + for (int j = 0; j < SOC_ETM_CHANNELS_PER_GROUP; 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])); + } + + TEST_ESP_OK(esp_etm_channel_enable(etm_chans[0][0])); + TEST_ESP_OK(esp_etm_dump(stdout)); + + 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++) { + TEST_ESP_OK(esp_etm_del_channel(etm_chans[i][j])); + } + } +} diff --git a/components/esp_hw_support/test_apps/etm/pytest_etm.py b/components/esp_hw_support/test_apps/etm/pytest_etm.py new file mode 100644 index 0000000000..6a795fe95d --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/pytest_etm.py @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32c6 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'release', + ], + indirect=True, +) +def test_etm(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('*') + dut.expect_unity_test_output() diff --git a/components/esp_hw_support/test_apps/etm/sdkconfig.ci.release b/components/esp_hw_support/test_apps/etm/sdkconfig.ci.release new file mode 100644 index 0000000000..91d93f163e --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/sdkconfig.ci.release @@ -0,0 +1,5 @@ +CONFIG_PM_ENABLE=y +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/components/esp_hw_support/test_apps/etm/sdkconfig.defaults b/components/esp_hw_support/test_apps/etm/sdkconfig.defaults new file mode 100644 index 0000000000..b308cb2ddd --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=n diff --git a/components/hal/CMakeLists.txt b/components/hal/CMakeLists.txt index 87bd8d0a5b..bcf4d99e04 100644 --- a/components/hal/CMakeLists.txt +++ b/components/hal/CMakeLists.txt @@ -85,6 +85,10 @@ if(NOT BOOTLOADER_BUILD) list(APPEND srcs "emac_hal.c") endif() + if(CONFIG_SOC_ETM_SUPPORTED) + list(APPEND srcs "etm_hal.c") + endif() + if(CONFIG_SOC_ADC_DMA_SUPPORTED) list(APPEND srcs "adc_hal.c") endif() diff --git a/components/hal/esp32c6/include/hal/clk_gate_ll.h b/components/hal/esp32c6/include/hal/clk_gate_ll.h index 10c5a999ae..5d664bbea9 100644 --- a/components/hal/esp32c6/include/hal/clk_gate_ll.h +++ b/components/hal/esp32c6/include/hal/clk_gate_ll.h @@ -56,6 +56,8 @@ static inline uint32_t periph_ll_get_clk_en_mask(periph_module_t periph) return PCR_GDMA_CLK_EN; case PERIPH_MCPWM0_MODULE: return PCR_PWM_CLK_EN; + case PERIPH_ETM_MODULE: + return PCR_ETM_CLK_EN; case PERIPH_AES_MODULE: return PCR_AES_CLK_EN; case PERIPH_SHA_MODULE: @@ -128,6 +130,8 @@ static inline uint32_t periph_ll_get_rst_en_mask(periph_module_t periph, bool en return PCR_GDMA_RST_EN; case PERIPH_MCPWM0_MODULE: return PCR_PWM_RST_EN; + case PERIPH_ETM_MODULE: + return PCR_ETM_RST_EN; case PERIPH_ECC_MODULE: return PCR_ECC_RST_EN; case PERIPH_TEMPSENSOR_MODULE: @@ -224,6 +228,8 @@ static uint32_t periph_ll_get_clk_en_reg(periph_module_t periph) return PCR_GDMA_CONF_REG; case PERIPH_MCPWM0_MODULE: return PCR_PWM_CONF_REG; + case PERIPH_ETM_MODULE: + return PCR_ETM_CONF_REG; case PERIPH_AES_MODULE: return PCR_AES_CONF_REG; case PERIPH_SHA_MODULE: @@ -282,6 +288,8 @@ static uint32_t periph_ll_get_rst_en_reg(periph_module_t periph) return PCR_GDMA_CONF_REG; case PERIPH_MCPWM0_MODULE: return PCR_PWM_CONF_REG; + case PERIPH_ETM_MODULE: + return PCR_ETM_CONF_REG; case PERIPH_AES_MODULE: return PCR_AES_CONF_REG; case PERIPH_SHA_MODULE: diff --git a/components/hal/esp32c6/include/hal/etm_ll.h b/components/hal/esp32c6/include/hal/etm_ll.h new file mode 100644 index 0000000000..9d95b485c2 --- /dev/null +++ b/components/hal/esp32c6/include/hal/etm_ll.h @@ -0,0 +1,103 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Note that most of the register operations in this layer are non-atomic operations. + +#pragma once + +#include +#include "hal/assert.h" +#include "hal/misc.h" +#include "soc/soc_etm_struct.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enable the clock for ETM module + * + * @param hw ETM register base address + * @param enable true to enable, false to disable + */ +static inline void etm_ll_enable_clock(soc_etm_dev_t *hw, bool enable) +{ + hw->clk_en.clk_en = enable; +} + +/** + * @brief Enable ETM channel + * + * @param hw ETM register base address + * @param chan Channel ID + */ +static inline void etm_ll_enable_channel(soc_etm_dev_t *hw, uint32_t chan) +{ + if (chan < 32) { + hw->ch_ena_ad0_set.val = 1 << chan; + } else { + hw->ch_ena_ad1_set.val = 1 << (chan - 32); + } +} + +/** + * @brief Disable ETM channel + * + * @param hw ETM register base address + * @param chan Channel ID + */ +static inline void etm_ll_disable_channel(soc_etm_dev_t *hw, uint32_t chan) +{ + if (chan < 32) { + hw->ch_ena_ad0_clr.val = 1 << chan; + } else { + hw->ch_ena_ad1_clr.val = 1 << (chan - 32); + } +} + +/** + * @brief Check whether the ETM channel is enabled or not + * + * @param hw ETM register base address + * @param chan Channel ID + * @return true if the channel is enabled, false otherwise + */ +static inline bool etm_ll_is_channel_enabled(soc_etm_dev_t *hw, uint32_t chan) +{ + if (chan < 32) { + return hw->ch_ena_ad0.val & (1 << chan); + } else { + return hw->ch_ena_ad1.val & (1 << (chan - 32)); + } +} + +/** + * @brief Set the input event for the ETM channel + * + * @param hw ETM register base address + * @param chan Channel ID + * @param event Event ID + */ +static inline void etm_ll_channel_set_event(soc_etm_dev_t *hw, uint32_t chan, uint32_t event) +{ + hw->channel[chan].evt_id.evt_id = event; +} + +/** + * @brief Set the output task for the ETM channel + * + * @param hw ETM register base address + * @param chan Channel ID + * @param task Task ID + */ +static inline void etm_ll_channel_set_task(soc_etm_dev_t *hw, uint32_t chan, uint32_t task) +{ + hw->channel[chan].task_id.task_id = task; +} + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/etm_hal.c b/components/hal/etm_hal.c new file mode 100644 index 0000000000..5e90b71189 --- /dev/null +++ b/components/hal/etm_hal.c @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "hal/etm_hal.h" +#include "hal/etm_ll.h" + +void etm_hal_init(etm_hal_context_t *hal) +{ + hal->regs = &SOC_ETM; +} + +void etm_hal_deinit(etm_hal_context_t *hal) +{ + hal->regs = NULL; +} diff --git a/components/hal/include/hal/etm_hal.h b/components/hal/include/hal/etm_hal.h new file mode 100644 index 0000000000..2ba4529ca7 --- /dev/null +++ b/components/hal/include/hal/etm_hal.h @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/******************************************************************************* + * NOTICE + * The hal is not public api, don't use in application code. + * See readme.md in hal/include/hal/readme.md + ******************************************************************************/ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct soc_etm_dev_t *etm_soc_handle_t; // ETM SOC layer handle + +/** + * @brief HAL context type of ETM driver + */ +typedef struct { + etm_soc_handle_t regs; /*!< ETM Register base address */ +} etm_hal_context_t; + +/** + * @brief Initialize the ETM HAL driver + * + * @param hal: ETM HAL context + */ +void etm_hal_init(etm_hal_context_t *hal); + +/** + * @brief Deinitialize the ETM HAL driver + * + * @param hal: ETM HAL context + */ +void etm_hal_deinit(etm_hal_context_t *hal); + +#ifdef __cplusplus +} +#endif diff --git a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in index a3afacf626..403ef3c9d7 100644 --- a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in @@ -23,6 +23,10 @@ config SOC_TWAI_SUPPORTED bool default y +config SOC_ETM_SUPPORTED + bool + default y + config SOC_BT_SUPPORTED bool default y @@ -243,6 +247,14 @@ config SOC_GDMA_PAIRS_PER_GROUP int default 3 +config SOC_ETM_GROUPS + int + default 1 + +config SOC_ETM_CHANNELS_PER_GROUP + int + default 50 + config SOC_GPIO_PORT int default 1 diff --git a/components/soc/esp32c6/include/soc/periph_defs.h b/components/soc/esp32c6/include/soc/periph_defs.h index 826ebe6dd6..0214b03077 100644 --- a/components/soc/esp32c6/include/soc/periph_defs.h +++ b/components/soc/esp32c6/include/soc/periph_defs.h @@ -40,6 +40,7 @@ typedef enum { PERIPH_DS_MODULE, PERIPH_GDMA_MODULE, PERIPH_MCPWM0_MODULE, + PERIPH_ETM_MODULE, PERIPH_SYSTIMER_MODULE, PERIPH_SARADC_MODULE, PERIPH_TEMPSENSOR_MODULE, diff --git a/components/soc/esp32c6/include/soc/soc_caps.h b/components/soc/esp32c6/include/soc/soc_caps.h index dd5d8f27fc..939fbf4a67 100644 --- a/components/soc/esp32c6/include/soc/soc_caps.h +++ b/components/soc/esp32c6/include/soc/soc_caps.h @@ -31,6 +31,7 @@ #define SOC_PCNT_SUPPORTED 1 #define SOC_MCPWM_SUPPORTED 1 #define SOC_TWAI_SUPPORTED 1 +#define SOC_ETM_SUPPORTED 1 #define SOC_BT_SUPPORTED 1 #define SOC_ASYNC_MEMCPY_SUPPORTED 1 #define SOC_USB_SERIAL_JTAG_SUPPORTED 1 @@ -141,6 +142,10 @@ #define SOC_GDMA_GROUPS (1U) // Number of GDMA groups #define SOC_GDMA_PAIRS_PER_GROUP (3) // Number of GDMA pairs in each group +/*-------------------------- ETM CAPS --------------------------------------*/ +#define SOC_ETM_GROUPS 1U // Number of ETM groups +#define SOC_ETM_CHANNELS_PER_GROUP 50 // Number of ETM channels in the group + /*-------------------------- GPIO CAPS ---------------------------------------*/ // ESP32-C6 has 1 GPIO peripheral #define SOC_GPIO_PORT (1U) @@ -249,7 +254,7 @@ #define SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP (3) ///< The number of GPIO synchros that each group has #define SOC_MCPWM_SWSYNC_CAN_PROPAGATE (1) ///< Software sync event can be routed to its output #define SOC_MCPWM_SUPPORT_ETM (1) ///< Support ETM (Event Task Matrix) -#define SOC_MCPWM_CAPTURE_CLK_FROM_GROUP (1) ///< Capture timer shares clock with other PWM timers +#define SOC_MCPWM_CAPTURE_CLK_FROM_GROUP (1) ///< Capture timer shares clock with other PWM timers #define SOC_MCPWM_CLK_SUPPORT_PLL160M (1) ///< Support PLL160M as clock source #define SOC_MCPWM_CLK_SUPPORT_XTAL (1) ///< Support XTAL as clock source