1 /*
2  * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdlib.h>
8 #include <sys/cdefs.h>
9 #include <sys/lock.h>
10 #include "sdkconfig.h"
11 #if CONFIG_ETM_ENABLE_DEBUG_LOG
12 // The local log level must be defined before including esp_log.h
13 // Set the maximum log level for this source file
14 #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
15 #endif
16 #include "freertos/FreeRTOS.h"
17 #include "freertos/task.h"
18 #include "soc/soc_caps.h"
19 #include "soc/periph_defs.h"
20 #include "esp_log.h"
21 #include "esp_check.h"
22 #include "esp_heap_caps.h"
23 #include "esp_etm.h"
24 #include "hal/etm_hal.h"
25 #include "hal/etm_ll.h"
26 #include "esp_private/periph_ctrl.h"
27 #include "esp_private/etm_interface.h"
28 
29 #define ETM_MEM_ALLOC_CAPS   MALLOC_CAP_DEFAULT
30 
31 static const char *TAG = "etm";
32 
33 typedef struct etm_platform_t etm_platform_t;
34 typedef struct etm_group_t etm_group_t;
35 typedef struct esp_etm_channel_t esp_etm_channel_t;
36 
37 struct etm_platform_t {
38     _lock_t mutex;                        // platform level mutex lock
39     etm_group_t *groups[SOC_ETM_GROUPS];  // etm group pool
40     int group_ref_counts[SOC_ETM_GROUPS]; // reference count used to protect group install/uninstall
41 };
42 
43 struct etm_group_t {
44     int group_id;          // hardware group id
45     etm_hal_context_t hal; // hardware abstraction layer context
46     portMUX_TYPE spinlock; // to protect per-group register level concurrent access
47     esp_etm_channel_t *chans[SOC_ETM_CHANNELS_PER_GROUP];
48 };
49 
50 typedef enum {
51     ETM_CHAN_FSM_INIT,
52     ETM_CHAN_FSM_ENABLE,
53 } etm_chan_fsm_t;
54 
55 struct esp_etm_channel_t {
56     int chan_id;        // Channel ID
57     etm_group_t *group; // which group this channel belongs to
58     etm_chan_fsm_t fsm; // record ETM channel's driver state
59     esp_etm_event_handle_t event; // which event is connect to the channel
60     esp_etm_task_handle_t task;   // which task is connect to the channel
61 };
62 
63 // ETM driver platform, it's always a singleton
64 static etm_platform_t s_platform;
65 
etm_acquire_group_handle(int group_id)66 static etm_group_t *etm_acquire_group_handle(int group_id)
67 {
68     bool new_group = false;
69     etm_group_t *group = NULL;
70 
71     // prevent install ETM group concurrently
72     _lock_acquire(&s_platform.mutex);
73     if (!s_platform.groups[group_id]) {
74         group = heap_caps_calloc(1, sizeof(etm_group_t), ETM_MEM_ALLOC_CAPS);
75         if (group) {
76             new_group = true;
77             s_platform.groups[group_id] = group; // register to platform
78             // initialize ETM group members
79             group->group_id = group_id;
80             group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
81             // enable APB access ETM registers
82             // if we have multiple ETM groups/instances, we assume the peripheral defines are continuous
83             periph_module_enable(PERIPH_ETM_MODULE + group_id);
84             periph_module_reset(PERIPH_ETM_MODULE + group_id);
85             // initialize HAL context
86             etm_hal_init(&group->hal);
87         }
88     } else {
89         group = s_platform.groups[group_id];
90     }
91     if (group) {
92         // someone acquired the group handle means we have a new object that refer to this group
93         s_platform.group_ref_counts[group_id]++;
94     }
95     _lock_release(&s_platform.mutex);
96 
97     if (new_group) {
98         ESP_LOGD(TAG, "new group (%d) at %p", group_id, group);
99     }
100 
101     return group;
102 }
103 
etm_release_group_handle(etm_group_t * group)104 static void etm_release_group_handle(etm_group_t *group)
105 {
106     int group_id = group->group_id;
107     bool do_deinitialize = false;
108 
109     _lock_acquire(&s_platform.mutex);
110     s_platform.group_ref_counts[group_id]--;
111     if (s_platform.group_ref_counts[group_id] == 0) {
112         assert(s_platform.groups[group_id]);
113         do_deinitialize = true;
114         s_platform.groups[group_id] = NULL; // deregister from platform
115         periph_module_disable(PERIPH_ETM_MODULE + group_id);
116     }
117     _lock_release(&s_platform.mutex);
118 
119     if (do_deinitialize) {
120         free(group);
121         ESP_LOGD(TAG, "del group (%d)", group_id);
122     }
123 }
124 
etm_chan_register_to_group(esp_etm_channel_t * chan)125 static esp_err_t etm_chan_register_to_group(esp_etm_channel_t *chan)
126 {
127     etm_group_t *group = NULL;
128     int chan_id = -1;
129     for (int i = 0; i < SOC_ETM_GROUPS; i++) {
130         group = etm_acquire_group_handle(i);
131         ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", i);
132         // loop to search free channel in the group
133         portENTER_CRITICAL(&group->spinlock);
134         for (int j = 0; j < SOC_ETM_CHANNELS_PER_GROUP; j++) {
135             if (!group->chans[j]) {
136                 chan_id = j;
137                 group->chans[j] = chan;
138                 break;
139             }
140         }
141         portEXIT_CRITICAL(&group->spinlock);
142         if (chan_id < 0) {
143             etm_release_group_handle(group);
144             group = NULL;
145         } else {
146             chan->chan_id = chan_id;
147             chan->group = group;
148             break;;
149         }
150     }
151     ESP_RETURN_ON_FALSE(chan_id != -1, ESP_ERR_NOT_FOUND, TAG, "no free channel");
152     return ESP_OK;
153 }
154 
etm_chan_unregister_from_group(esp_etm_channel_t * chan)155 static void etm_chan_unregister_from_group(esp_etm_channel_t *chan)
156 {
157     etm_group_t *group = chan->group;
158     int chan_id = chan->chan_id;
159     portENTER_CRITICAL(&group->spinlock);
160     group->chans[chan_id] = NULL;
161     portEXIT_CRITICAL(&group->spinlock);
162     // channel has a reference on group, release it now
163     etm_release_group_handle(group);
164 }
165 
etm_chan_destroy(esp_etm_channel_t * chan)166 static esp_err_t etm_chan_destroy(esp_etm_channel_t *chan)
167 {
168     if (chan->group) {
169         etm_chan_unregister_from_group(chan);
170     }
171     free(chan);
172     return ESP_OK;
173 }
174 
esp_etm_new_channel(const esp_etm_channel_config_t * config,esp_etm_channel_handle_t * ret_chan)175 esp_err_t esp_etm_new_channel(const esp_etm_channel_config_t *config, esp_etm_channel_handle_t *ret_chan)
176 {
177 #if CONFIG_ETM_ENABLE_DEBUG_LOG
178     esp_log_level_set(TAG, ESP_LOG_DEBUG);
179 #endif
180     esp_err_t ret = ESP_OK;
181     esp_etm_channel_t *chan = NULL;
182     ESP_GOTO_ON_FALSE(config && ret_chan, ESP_ERR_INVALID_ARG, err, TAG, "invalid args");
183 
184     chan = heap_caps_calloc(1, sizeof(esp_etm_channel_t), ETM_MEM_ALLOC_CAPS);
185     ESP_GOTO_ON_FALSE(chan, ESP_ERR_NO_MEM, err, TAG, "no mem for channel");
186     // register channel to the group, one group can have multiple channels
187     ESP_GOTO_ON_ERROR(etm_chan_register_to_group(chan), err, TAG, "register channel failed");
188     etm_group_t *group = chan->group;
189     int group_id = group->group_id;
190     int chan_id = chan->chan_id;
191 
192     chan->fsm = ETM_CHAN_FSM_INIT;
193     ESP_LOGD(TAG, "new etm channel (%d,%d) at %p", group_id, chan_id, chan);
194     *ret_chan = chan;
195     return ESP_OK;
196 
197 err:
198     if (chan) {
199         etm_chan_destroy(chan);
200     }
201     return ret;
202 }
203 
esp_etm_del_channel(esp_etm_channel_handle_t chan)204 esp_err_t esp_etm_del_channel(esp_etm_channel_handle_t chan)
205 {
206     ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid args");
207     ESP_RETURN_ON_FALSE(chan->fsm == ETM_CHAN_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "channel is not in init state");
208     etm_group_t *group = chan->group;
209     int group_id = group->group_id;
210     int chan_id = chan->chan_id;
211 
212     // disconnect the channel from any event or task
213     etm_ll_channel_set_event(group->hal.regs, chan_id, 0);
214     etm_ll_channel_set_task(group->hal.regs, chan_id, 0);
215 
216     ESP_LOGD(TAG, "del etm channel (%d,%d)", group_id, chan_id);
217     // recycle memory resource
218     ESP_RETURN_ON_ERROR(etm_chan_destroy(chan), TAG, "destroy etm channel failed");
219     return ESP_OK;
220 }
221 
esp_etm_channel_enable(esp_etm_channel_handle_t chan)222 esp_err_t esp_etm_channel_enable(esp_etm_channel_handle_t chan)
223 {
224     ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
225     ESP_RETURN_ON_FALSE(chan->fsm == ETM_CHAN_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "channel is not in init state");
226     etm_group_t *group = chan->group;
227     etm_ll_enable_channel(group->hal.regs, chan->chan_id);
228     chan->fsm = ETM_CHAN_FSM_ENABLE;
229     return ESP_OK;
230 }
231 
esp_etm_channel_disable(esp_etm_channel_handle_t chan)232 esp_err_t esp_etm_channel_disable(esp_etm_channel_handle_t chan)
233 {
234     ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
235     ESP_RETURN_ON_FALSE(chan->fsm == ETM_CHAN_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "channel not in enable state");
236     etm_group_t *group = chan->group;
237     etm_ll_disable_channel(group->hal.regs, chan->chan_id);
238     chan->fsm = ETM_CHAN_FSM_INIT;
239     return ESP_OK;
240 }
241 
esp_etm_channel_connect(esp_etm_channel_handle_t chan,esp_etm_event_handle_t event,esp_etm_task_handle_t task)242 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)
243 {
244     ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
245     etm_group_t *group = chan->group;
246     uint32_t event_id = 0;
247     uint32_t task_id = 0;
248 
249     // if the event/task is NULL, then the channel will disconnect from the event/task
250     if (event) {
251         event_id = event->event_id;
252     }
253     if (task) {
254         task_id = task->task_id;
255     }
256     etm_ll_channel_set_event(group->hal.regs, chan->chan_id, event_id);
257     etm_ll_channel_set_task(group->hal.regs, chan->chan_id, task_id);
258     chan->event = event;
259     chan->task = task;
260     ESP_LOGD(TAG, "event %"PRIu32" => channel %d", event_id, chan->chan_id);
261     ESP_LOGD(TAG, "channel %d => task %"PRIu32, chan->chan_id, task_id);
262     return ESP_OK;
263 }
264 
esp_etm_del_event(esp_etm_event_handle_t event)265 esp_err_t esp_etm_del_event(esp_etm_event_handle_t event)
266 {
267     ESP_RETURN_ON_FALSE(event, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
268     return event->del(event);
269 }
270 
esp_etm_del_task(esp_etm_task_handle_t task)271 esp_err_t esp_etm_del_task(esp_etm_task_handle_t task)
272 {
273     ESP_RETURN_ON_FALSE(task, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
274     return task->del(task);
275 }
276 
esp_etm_dump(FILE * out_stream)277 esp_err_t esp_etm_dump(FILE *out_stream)
278 {
279     etm_group_t *group = NULL;
280     esp_etm_channel_handle_t etm_chan = NULL;
281     ESP_RETURN_ON_FALSE(out_stream, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
282     fprintf(out_stream, "===========ETM Dump Start==========\r\n");
283     char line[80];
284     size_t len = sizeof(line);
285     for (int i = 0; i < SOC_ETM_GROUPS; i++) {
286         group = etm_acquire_group_handle(i);
287         ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", i);
288         etm_hal_context_t *hal = &group->hal;
289         for (int j = 0; j < SOC_ETM_CHANNELS_PER_GROUP; j++) {
290             bool print_line = true;
291             portENTER_CRITICAL(&group->spinlock);
292             etm_chan = group->chans[j];
293             if (etm_ll_is_channel_enabled(hal->regs, j)) {
294                 if (!etm_chan) {
295                     // in case the etm driver is bypassed and some channel is enabled in another way (e.g. by hal driver)
296                     snprintf(line, len, "channel %d is enabled but not recorded\r\n", j);
297                 } else {
298                     // print which event and task the channel is connected to
299                     snprintf(line, len, "channel %d: event %"PRIu32" ==> task %"PRIu32"\r\n", j,
300                              etm_chan->event ? etm_chan->event->event_id : 0,
301                              etm_chan->task ? etm_chan->task->task_id : 0);
302                 }
303             } else {
304                 if (etm_chan) {
305                     // channel is created, but not enabled by `esp_etm_channel_enable` yet
306                     snprintf(line, len, "channel %d is created but not enabled\r\n", j);
307                 } else {
308                     // a free channel, don't print anything
309                     print_line = false;
310                 }
311             }
312             portEXIT_CRITICAL(&group->spinlock);
313             if (print_line) {
314                 fputs(line, out_stream);
315             }
316         }
317         etm_release_group_handle(group);
318     }
319     fprintf(out_stream, "===========ETM Dump End============\r\n");
320     return ESP_OK;
321 }
322