1 // Copyright 2020 Espressif Systems (Shanghai) PTE LTD
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 // #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
16
17 #include <stdlib.h>
18 #include <sys/cdefs.h>
19 #include "freertos/FreeRTOS.h"
20 #include "freertos/task.h"
21 #include "soc/soc_caps.h"
22 #include "soc/periph_defs.h"
23 #include "esp_intr_alloc.h"
24 #include "esp_log.h"
25 #include "driver/periph_ctrl.h"
26 #include "esp_private/gdma.h"
27 #include "hal/gdma_hal.h"
28 #include "hal/gdma_ll.h"
29 #include "soc/gdma_periph.h"
30
31 static const char *TAG = "gdma";
32
33 #define DMA_CHECK(a, msg, tag, ret, ...) \
34 do { \
35 if (unlikely(!(a))) { \
36 ESP_LOGE(TAG, "%s(%d): " msg, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
37 ret_code = ret; \
38 goto tag; \
39 } \
40 } while (0)
41
42 #define GDMA_INVALID_PERIPH_TRIG (0x3F)
43 #define SEARCH_REQUEST_RX_CHANNEL (1 << 0)
44 #define SEARCH_REQUEST_TX_CHANNEL (1 << 1)
45
46 typedef struct gdma_platform_t gdma_platform_t;
47 typedef struct gdma_group_t gdma_group_t;
48 typedef struct gdma_pair_t gdma_pair_t;
49 typedef struct gdma_channel_t gdma_channel_t;
50 typedef struct gdma_tx_channel_t gdma_tx_channel_t;
51 typedef struct gdma_rx_channel_t gdma_rx_channel_t;
52
53 /**
54 * GDMA driver consists of there object class, namely: Group, Pair and Channel.
55 * Channel is allocated when user calls `gdma_new_channel`, its lifecycle is maintained by user.
56 * Pair and Group are all lazy allocated, their life cycles are maintained by this driver.
57 * We use reference count to track their life cycles, i.e. the driver will free their memory only when their reference count reached to 0.
58 *
59 * We don't use an all-in-one spin lock in this driver, instead, we created different spin locks at different level.
60 * For platform, it has a spinlock, which is used to protect the group handle slots and reference count of each group.
61 * For group, it has a spinlock, which is used to protect group level stuffs, e.g. hal object, pair handle slots and reference count of each pair.
62 * For pair, it has a sinlock, which is used to protect pair level stuffs, e.g. interrupt handle, channel handle slots, occupy code.
63 */
64
65 struct gdma_platform_t {
66 portMUX_TYPE spinlock; // platform level spinlock
67 gdma_group_t *groups[SOC_GDMA_GROUPS]; // array of GDMA group instances
68 int group_ref_counts[SOC_GDMA_GROUPS]; // reference count used to protect group install/uninstall
69 };
70
71 struct gdma_group_t {
72 int group_id; // Group ID, index from 0
73 gdma_hal_context_t hal; // HAL instance is at group level
74 portMUX_TYPE spinlock; // group level spinlock
75 gdma_pair_t *pairs[SOC_GDMA_PAIRS_PER_GROUP]; // handles of GDMA pairs
76 int pair_ref_counts[SOC_GDMA_PAIRS_PER_GROUP]; // reference count used to protect pair install/uninstall
77 };
78
79 struct gdma_pair_t {
80 gdma_group_t *group; // which group the pair belongs to
81 int pair_id; // Pair ID, index from 0
82 gdma_tx_channel_t *tx_chan; // pointer of tx channel in the pair
83 gdma_rx_channel_t *rx_chan; // pointer of rx channel in the pair
84 int occupy_code; // each bit indicates which channel has been occupied (an occupied channel will be skipped during channel search)
85 intr_handle_t intr; // Interrupt is at pair level
86 portMUX_TYPE spinlock; // pair level spinlock
87 };
88
89 struct gdma_channel_t {
90 gdma_pair_t *pair; // which pair the channel belongs to
91 gdma_channel_direction_t direction; // channel direction
92 int periph_id; // Peripheral instance ID, indicates which peripheral is connected to this GDMA channel
93 esp_err_t (*del)(gdma_channel_t *channel); // channel deletion function, it's polymorphic, see `gdma_del_tx_channel` or `gdma_del_rx_channel`
94 };
95
96 struct gdma_tx_channel_t {
97 gdma_channel_t base; // GDMA channel, base class
98 void *user_data; // user registered DMA event data
99 gdma_event_callback_t on_trans_eof; // TX EOF callback
100 };
101
102 struct gdma_rx_channel_t {
103 gdma_channel_t base; // GDMA channel, base class
104 void *user_data; // user registered DMA event data
105 gdma_event_callback_t on_recv_eof; // RX EOF callback
106 };
107
108 static gdma_group_t *gdma_acquire_group_handle(int group_id);
109 static void gdma_release_group_handle(gdma_group_t *group);
110 static gdma_pair_t *gdma_acquire_pair_handle(gdma_group_t *group, int pair_id);
111 static void gdma_release_pair_handle(gdma_pair_t *pair);
112 static void gdma_uninstall_group(gdma_group_t *group);
113 static void gdma_uninstall_pair(gdma_pair_t *pair);
114 static esp_err_t gdma_del_tx_channel(gdma_channel_t *dma_channel);
115 static esp_err_t gdma_del_rx_channel(gdma_channel_t *dma_channel);
116 static esp_err_t gdma_install_interrupt(gdma_pair_t *pair);
117
118 // gdma driver platform
119 static gdma_platform_t s_platform = {
120 .spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED,
121 .groups = {} // groups will be lazy installed
122 };
123
gdma_new_channel(const gdma_channel_alloc_config_t * config,gdma_channel_handle_t * ret_chan)124 esp_err_t gdma_new_channel(const gdma_channel_alloc_config_t *config, gdma_channel_handle_t *ret_chan)
125 {
126 esp_err_t ret_code = ESP_OK;
127 gdma_tx_channel_t *alloc_tx_channel = NULL;
128 gdma_rx_channel_t *alloc_rx_channel = NULL;
129 int search_code = 0;
130 gdma_pair_t *pair = NULL;
131 gdma_group_t *group = NULL;
132 DMA_CHECK(config && ret_chan, "invalid argument", err, ESP_ERR_INVALID_ARG);
133
134 if (config->flags.reserve_sibling) {
135 search_code = SEARCH_REQUEST_RX_CHANNEL | SEARCH_REQUEST_TX_CHANNEL; // search for a pair of channels
136 }
137 if (config->direction == GDMA_CHANNEL_DIRECTION_TX) {
138 search_code |= SEARCH_REQUEST_TX_CHANNEL; // search TX only
139 alloc_tx_channel = calloc(1, sizeof(gdma_tx_channel_t));
140 DMA_CHECK(alloc_tx_channel, "no mem for gdma tx channel", err, ESP_ERR_NO_MEM);
141 } else if (config->direction == GDMA_CHANNEL_DIRECTION_RX) {
142 search_code |= SEARCH_REQUEST_RX_CHANNEL; // search RX only
143 alloc_rx_channel = calloc(1, sizeof(gdma_rx_channel_t));
144 DMA_CHECK(alloc_rx_channel, "no mem for gdma rx channel", err, ESP_ERR_NO_MEM);
145 }
146
147 if (config->sibling_chan) {
148 pair = config->sibling_chan->pair;
149 DMA_CHECK(pair, "invalid sibling channel", err, ESP_ERR_INVALID_ARG);
150 DMA_CHECK(config->sibling_chan->direction != config->direction,
151 "sibling channel should have a different direction", err, ESP_ERR_INVALID_ARG);
152 group = pair->group;
153 portENTER_CRITICAL(&group->spinlock);
154 group->pair_ref_counts[pair->pair_id]++; // channel obtains a reference to pair
155 portEXIT_CRITICAL(&group->spinlock);
156 goto search_done; // skip the search path below if user has specify a sibling channel
157 }
158
159 for (int i = 0; i < SOC_GDMA_GROUPS && search_code; i++) { // loop to search group
160 group = gdma_acquire_group_handle(i);
161 for (int j = 0; j < SOC_GDMA_PAIRS_PER_GROUP && search_code && group; j++) { // loop to search pair
162 pair = gdma_acquire_pair_handle(group, j);
163 if (pair) {
164 portENTER_CRITICAL(&pair->spinlock);
165 if (!(search_code & pair->occupy_code)) { // pair has suitable position for acquired channel(s)
166 pair->occupy_code |= search_code;
167 search_code = 0; // exit search loop
168 }
169 portEXIT_CRITICAL(&pair->spinlock);
170 if (!search_code) {
171 portENTER_CRITICAL(&group->spinlock);
172 group->pair_ref_counts[j]++; // channel obtains a reference to pair
173 portEXIT_CRITICAL(&group->spinlock);
174 }
175 }
176 gdma_release_pair_handle(pair);
177 } // loop used to search pair
178 gdma_release_group_handle(group);
179 } // loop used to search group
180 DMA_CHECK(search_code == 0, "no free gdma channel, search code=%d", err, ESP_ERR_NOT_FOUND, search_code);
181
182 search_done:
183 // register TX channel
184 if (alloc_tx_channel) {
185 pair->tx_chan = alloc_tx_channel;
186 alloc_tx_channel->base.pair = pair;
187 alloc_tx_channel->base.direction = GDMA_CHANNEL_DIRECTION_TX;
188 alloc_tx_channel->base.periph_id = GDMA_INVALID_PERIPH_TRIG;
189 alloc_tx_channel->base.del = gdma_del_tx_channel; // set channel deletion function
190 *ret_chan = &alloc_tx_channel->base; // return the installed channel
191 }
192
193 // register RX channel
194 if (alloc_rx_channel) {
195 pair->rx_chan = alloc_rx_channel;
196 alloc_rx_channel->base.pair = pair;
197 alloc_rx_channel->base.direction = GDMA_CHANNEL_DIRECTION_RX;
198 alloc_rx_channel->base.periph_id = GDMA_INVALID_PERIPH_TRIG;
199 alloc_rx_channel->base.del = gdma_del_rx_channel; // set channel deletion function
200 *ret_chan = &alloc_rx_channel->base; // return the installed channel
201 }
202
203 ESP_LOGD(TAG, "new %s channel (%d,%d) at %p", (config->direction == GDMA_CHANNEL_DIRECTION_TX) ? "tx" : "rx",
204 group->group_id, pair->pair_id, *ret_chan);
205 return ESP_OK;
206
207 err:
208 if (alloc_tx_channel) {
209 free(alloc_tx_channel);
210 }
211 if (alloc_rx_channel) {
212 free(alloc_rx_channel);
213 }
214 return ret_code;
215 }
216
gdma_del_channel(gdma_channel_handle_t dma_chan)217 esp_err_t gdma_del_channel(gdma_channel_handle_t dma_chan)
218 {
219 esp_err_t ret_code = ESP_OK;
220 DMA_CHECK(dma_chan, "invalid argument", err, ESP_ERR_INVALID_ARG);
221
222 ret_code = dma_chan->del(dma_chan); // call `gdma_del_tx_channel` or `gdma_del_rx_channel`
223
224 err:
225 return ret_code;
226 }
227
gdma_get_channel_id(gdma_channel_handle_t dma_chan,int * channel_id)228 esp_err_t gdma_get_channel_id(gdma_channel_handle_t dma_chan, int *channel_id)
229 {
230 esp_err_t ret_code = ESP_OK;
231 gdma_pair_t *pair = NULL;
232 DMA_CHECK(dma_chan, "invalid argument", err, ESP_ERR_INVALID_ARG);
233 pair = dma_chan->pair;
234 *channel_id = pair->pair_id;
235 err:
236 return ret_code;
237 }
238
gdma_connect(gdma_channel_handle_t dma_chan,gdma_trigger_t trig_periph)239 esp_err_t gdma_connect(gdma_channel_handle_t dma_chan, gdma_trigger_t trig_periph)
240 {
241 esp_err_t ret_code = ESP_OK;
242 gdma_pair_t *pair = NULL;
243 gdma_group_t *group = NULL;
244 DMA_CHECK(dma_chan, "invalid argument", err, ESP_ERR_INVALID_ARG);
245 DMA_CHECK(dma_chan->periph_id == GDMA_INVALID_PERIPH_TRIG, "channel is using by peripheral: %d", err, ESP_ERR_INVALID_STATE, dma_chan->periph_id);
246 pair = dma_chan->pair;
247 group = pair->group;
248
249 dma_chan->periph_id = trig_periph.instance_id;
250 // enable/disable m2m mode
251 gdma_ll_enable_m2m_mode(group->hal.dev, pair->pair_id, trig_periph.periph == GDMA_TRIG_PERIPH_M2M);
252
253 if (dma_chan->direction == GDMA_CHANNEL_DIRECTION_TX) {
254 gdma_ll_tx_reset_channel(group->hal.dev, pair->pair_id); // reset channel
255 if (trig_periph.periph != GDMA_TRIG_PERIPH_M2M) {
256 gdma_ll_tx_connect_to_periph(group->hal.dev, pair->pair_id, trig_periph.instance_id);
257 }
258 } else {
259 gdma_ll_rx_reset_channel(group->hal.dev, pair->pair_id); // reset channel
260 if (trig_periph.periph != GDMA_TRIG_PERIPH_M2M) {
261 gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id, trig_periph.instance_id);
262 }
263 }
264
265 err:
266 return ret_code;
267 }
268
gdma_disconnect(gdma_channel_handle_t dma_chan)269 esp_err_t gdma_disconnect(gdma_channel_handle_t dma_chan)
270 {
271 esp_err_t ret_code = ESP_OK;
272 gdma_pair_t *pair = NULL;
273 gdma_group_t *group = NULL;
274 DMA_CHECK(dma_chan, "invalid argument", err, ESP_ERR_INVALID_ARG);
275 DMA_CHECK(dma_chan->periph_id != GDMA_INVALID_PERIPH_TRIG, "no peripheral is connected to the channel", err, ESP_ERR_INVALID_STATE);
276 pair = dma_chan->pair;
277 group = pair->group;
278
279 dma_chan->periph_id = GDMA_INVALID_PERIPH_TRIG;
280 if (dma_chan->direction == GDMA_CHANNEL_DIRECTION_TX) {
281 gdma_ll_tx_connect_to_periph(group->hal.dev, pair->pair_id, GDMA_INVALID_PERIPH_TRIG);
282 } else {
283 gdma_ll_rx_connect_to_periph(group->hal.dev, pair->pair_id, GDMA_INVALID_PERIPH_TRIG);
284 }
285
286 err:
287 return ret_code;
288 }
289
gdma_apply_strategy(gdma_channel_handle_t dma_chan,const gdma_strategy_config_t * config)290 esp_err_t gdma_apply_strategy(gdma_channel_handle_t dma_chan, const gdma_strategy_config_t *config)
291 {
292 esp_err_t ret_code = ESP_OK;
293 gdma_pair_t *pair = NULL;
294 gdma_group_t *group = NULL;
295 DMA_CHECK(dma_chan, "invalid argument", err, ESP_ERR_INVALID_ARG);
296 pair = dma_chan->pair;
297 group = pair->group;
298
299 if (dma_chan->direction == GDMA_CHANNEL_DIRECTION_TX) {
300 gdma_ll_tx_enable_owner_check(group->hal.dev, pair->pair_id, config->owner_check);
301 gdma_ll_tx_enable_auto_write_back(group->hal.dev, pair->pair_id, config->auto_update_desc);
302 } else {
303 gdma_ll_rx_enable_owner_check(group->hal.dev, pair->pair_id, config->owner_check);
304 }
305
306 err:
307 return ret_code;
308 }
309
gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan,gdma_tx_event_callbacks_t * cbs,void * user_data)310 esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_tx_event_callbacks_t *cbs, void *user_data)
311 {
312 esp_err_t ret_code = ESP_OK;
313 gdma_pair_t *pair = NULL;
314 gdma_group_t *group = NULL;
315 DMA_CHECK(dma_chan && dma_chan->direction == GDMA_CHANNEL_DIRECTION_TX, "invalid argument", err, ESP_ERR_INVALID_ARG);
316 pair = dma_chan->pair;
317 group = pair->group;
318 gdma_tx_channel_t *tx_chan = __containerof(dma_chan, gdma_tx_channel_t, base);
319
320 // lazy install interrupt service
321 DMA_CHECK(gdma_install_interrupt(pair) == ESP_OK, "install interrupt service failed", err, ESP_FAIL);
322
323 // enable/disable GDMA interrupt events for TX channel
324 portENTER_CRITICAL(&pair->spinlock);
325 gdma_ll_enable_interrupt(group->hal.dev, pair->pair_id, GDMA_LL_EVENT_TX_EOF, cbs->on_trans_eof != NULL);
326 portEXIT_CRITICAL(&pair->spinlock);
327
328 tx_chan->on_trans_eof = cbs->on_trans_eof;
329 tx_chan->user_data = user_data;
330
331 DMA_CHECK(esp_intr_enable(pair->intr) == ESP_OK, "enable interrupt failed", err, ESP_FAIL);
332
333 err:
334 return ret_code;
335 }
336
gdma_register_rx_event_callbacks(gdma_channel_handle_t dma_chan,gdma_rx_event_callbacks_t * cbs,void * user_data)337 esp_err_t gdma_register_rx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_rx_event_callbacks_t *cbs, void *user_data)
338 {
339 esp_err_t ret_code = ESP_OK;
340 gdma_pair_t *pair = NULL;
341 gdma_group_t *group = NULL;
342 DMA_CHECK(dma_chan && dma_chan->direction == GDMA_CHANNEL_DIRECTION_RX, "invalid argument", err, ESP_ERR_INVALID_ARG);
343 pair = dma_chan->pair;
344 group = pair->group;
345 gdma_rx_channel_t *rx_chan = __containerof(dma_chan, gdma_rx_channel_t, base);
346
347 // lazy install interrupt service
348 DMA_CHECK(gdma_install_interrupt(pair) == ESP_OK, "install interrupt service failed", err, ESP_FAIL);
349
350 // enable/disable GDMA interrupt events for RX channel
351 portENTER_CRITICAL(&pair->spinlock);
352 gdma_ll_enable_interrupt(group->hal.dev, pair->pair_id, GDMA_LL_EVENT_RX_SUC_EOF, cbs->on_recv_eof != NULL);
353 portEXIT_CRITICAL(&pair->spinlock);
354
355 rx_chan->on_recv_eof = cbs->on_recv_eof;
356 rx_chan->user_data = user_data;
357
358 DMA_CHECK(esp_intr_enable(pair->intr) == ESP_OK, "enable interrupt failed", err, ESP_FAIL);
359
360 err:
361 return ret_code;
362 }
363
gdma_start(gdma_channel_handle_t dma_chan,intptr_t desc_base_addr)364 esp_err_t gdma_start(gdma_channel_handle_t dma_chan, intptr_t desc_base_addr)
365 {
366 esp_err_t ret_code = ESP_OK;
367 gdma_pair_t *pair = NULL;
368 gdma_group_t *group = NULL;
369 DMA_CHECK(dma_chan, "invalid argument", err, ESP_ERR_INVALID_ARG);
370 pair = dma_chan->pair;
371 group = pair->group;
372
373 if (dma_chan->direction == GDMA_CHANNEL_DIRECTION_RX) {
374 gdma_ll_rx_set_desc_addr(group->hal.dev, pair->pair_id, desc_base_addr);
375 gdma_ll_rx_start(group->hal.dev, pair->pair_id);
376 } else {
377 gdma_ll_tx_set_desc_addr(group->hal.dev, pair->pair_id, desc_base_addr);
378 gdma_ll_tx_start(group->hal.dev, pair->pair_id);
379 }
380
381 err:
382 return ret_code;
383 }
384
gdma_stop(gdma_channel_handle_t dma_chan)385 esp_err_t gdma_stop(gdma_channel_handle_t dma_chan)
386 {
387 esp_err_t ret_code = ESP_OK;
388 gdma_pair_t *pair = NULL;
389 gdma_group_t *group = NULL;
390 DMA_CHECK(dma_chan, "invalid argument", err, ESP_ERR_INVALID_ARG);
391 pair = dma_chan->pair;
392 group = pair->group;
393
394 if (dma_chan->direction == GDMA_CHANNEL_DIRECTION_RX) {
395 gdma_ll_rx_stop(group->hal.dev, pair->pair_id);
396 } else {
397 gdma_ll_tx_stop(group->hal.dev, pair->pair_id);
398 }
399
400 err:
401 return ret_code;
402 }
403
gdma_append(gdma_channel_handle_t dma_chan)404 esp_err_t gdma_append(gdma_channel_handle_t dma_chan)
405 {
406 esp_err_t ret_code = ESP_OK;
407 gdma_pair_t *pair = NULL;
408 gdma_group_t *group = NULL;
409 DMA_CHECK(dma_chan, "invalid argument", err, ESP_ERR_INVALID_ARG);
410 pair = dma_chan->pair;
411 group = pair->group;
412
413 if (dma_chan->direction == GDMA_CHANNEL_DIRECTION_RX) {
414 gdma_ll_rx_restart(group->hal.dev, pair->pair_id);
415 } else {
416 gdma_ll_tx_restart(group->hal.dev, pair->pair_id);
417 }
418
419 err:
420 return ret_code;
421 }
422
gdma_uninstall_group(gdma_group_t * group)423 static void gdma_uninstall_group(gdma_group_t *group)
424 {
425 int group_id = group->group_id;
426 bool do_deinitialize = false;
427
428 portENTER_CRITICAL(&s_platform.spinlock);
429 s_platform.group_ref_counts[group_id]--;
430 if (s_platform.group_ref_counts[group_id] == 0) {
431 assert(s_platform.groups[group_id]);
432 do_deinitialize = true;
433 s_platform.groups[group_id] = NULL; // deregister from platfrom
434 gdma_ll_enable_clock(group->hal.dev, false);
435 periph_module_disable(gdma_periph_signals.groups[group_id].module);
436 }
437 portEXIT_CRITICAL(&s_platform.spinlock);
438
439 if (do_deinitialize) {
440 free(group);
441 ESP_LOGD(TAG, "del group %d", group_id);
442 }
443 }
444
gdma_acquire_group_handle(int group_id)445 static gdma_group_t *gdma_acquire_group_handle(int group_id)
446 {
447 bool new_group = false;
448 gdma_group_t *group = NULL;
449 gdma_group_t *pre_alloc_group = calloc(1, sizeof(gdma_group_t));
450 if (!pre_alloc_group) {
451 goto out;
452 }
453 portENTER_CRITICAL(&s_platform.spinlock);
454 if (!s_platform.groups[group_id]) {
455 new_group = true;
456 group = pre_alloc_group;
457 s_platform.groups[group_id] = group; // register to platform
458 group->group_id = group_id;
459 group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
460 periph_module_enable(gdma_periph_signals.groups[group_id].module); // enable APB to access GDMA registers
461 gdma_hal_init(&group->hal, group_id); // initialize HAL context
462 gdma_ll_enable_clock(group->hal.dev, true); // enable gdma clock
463 } else {
464 group = s_platform.groups[group_id];
465 }
466 // someone acquired the group handle means we have a new object that refer to this group
467 s_platform.group_ref_counts[group_id]++;
468 portEXIT_CRITICAL(&s_platform.spinlock);
469
470 if (new_group) {
471 ESP_LOGD(TAG, "new group (%d) at %p", group->group_id, group);
472 } else {
473 free(pre_alloc_group);
474 }
475 out:
476 return group;
477 }
478
gdma_release_group_handle(gdma_group_t * group)479 static void gdma_release_group_handle(gdma_group_t *group)
480 {
481 if (group) {
482 gdma_uninstall_group(group);
483 }
484 }
485
gdma_uninstall_pair(gdma_pair_t * pair)486 static void gdma_uninstall_pair(gdma_pair_t *pair)
487 {
488 gdma_group_t *group = pair->group;
489 int pair_id = pair->pair_id;
490 bool do_deinitialize = false;
491
492 portENTER_CRITICAL(&group->spinlock);
493 group->pair_ref_counts[pair_id]--;
494 if (group->pair_ref_counts[pair_id] == 0) {
495 assert(group->pairs[pair_id]);
496 do_deinitialize = true;
497 group->pairs[pair_id] = NULL; // deregister from pair
498 if (pair->intr) {
499 // disable interrupt handler (but not freed, esp_intr_free is a blocking API, we can't use it in a critical section)
500 esp_intr_disable(pair->intr);
501 gdma_ll_enable_interrupt(group->hal.dev, pair->pair_id, UINT32_MAX, false); // disable all interupt events
502 gdma_ll_clear_interrupt_status(group->hal.dev, pair->pair_id, UINT32_MAX); // clear all pending events
503 }
504 }
505 portEXIT_CRITICAL(&group->spinlock);
506
507 if (do_deinitialize) {
508 if (pair->intr) {
509 esp_intr_free(pair->intr); // free interrupt resource
510 ESP_LOGD(TAG, "uninstall interrupt service for pair (%d,%d)", group->group_id, pair_id);
511 }
512 free(pair);
513 ESP_LOGD(TAG, "del pair (%d,%d)", group->group_id, pair_id);
514
515 gdma_uninstall_group(group);
516 }
517 }
518
gdma_acquire_pair_handle(gdma_group_t * group,int pair_id)519 static gdma_pair_t *gdma_acquire_pair_handle(gdma_group_t *group, int pair_id)
520 {
521 bool new_pair = false;
522 gdma_pair_t *pair = NULL;
523 gdma_pair_t *pre_alloc_pair = calloc(1, sizeof(gdma_pair_t));
524 if (!pre_alloc_pair) {
525 goto out;
526 }
527 portENTER_CRITICAL(&group->spinlock);
528 if (!group->pairs[pair_id]) {
529 new_pair = true;
530 pair = pre_alloc_pair;
531 group->pairs[pair_id] = pair; // register to group
532 pair->group = group;
533 pair->pair_id = pair_id;
534 pair->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
535 } else {
536 pair = group->pairs[pair_id];
537 }
538 // someone acquired the pair handle means we have a new object that refer to this pair
539 group->pair_ref_counts[pair_id]++;
540 portEXIT_CRITICAL(&group->spinlock);
541
542 if (new_pair) {
543 portENTER_CRITICAL(&s_platform.spinlock);
544 s_platform.group_ref_counts[group->group_id]++; // pair obtains a reference to group
545 portEXIT_CRITICAL(&s_platform.spinlock);
546 ESP_LOGD(TAG, "new pair (%d,%d) at %p", group->group_id, pair->pair_id, pair);
547 } else {
548 free(pre_alloc_pair);
549 }
550 out:
551 return pair;
552 }
553
gdma_release_pair_handle(gdma_pair_t * pair)554 static void gdma_release_pair_handle(gdma_pair_t *pair)
555 {
556 if (pair) {
557 gdma_uninstall_pair(pair);
558 }
559 }
560
gdma_del_tx_channel(gdma_channel_t * dma_channel)561 static esp_err_t gdma_del_tx_channel(gdma_channel_t *dma_channel)
562 {
563 gdma_pair_t *pair = dma_channel->pair;
564 gdma_group_t *group = pair->group;
565 gdma_tx_channel_t *tx_chan = __containerof(dma_channel, gdma_tx_channel_t, base);
566 portENTER_CRITICAL(&pair->spinlock);
567 pair->tx_chan = NULL;
568 pair->occupy_code &= ~SEARCH_REQUEST_TX_CHANNEL;
569 portEXIT_CRITICAL(&pair->spinlock);
570
571 ESP_LOGD(TAG, "del tx channel (%d,%d)", group->group_id, pair->pair_id);
572 free(tx_chan);
573 gdma_uninstall_pair(pair);
574 return ESP_OK;
575 }
576
gdma_del_rx_channel(gdma_channel_t * dma_channel)577 static esp_err_t gdma_del_rx_channel(gdma_channel_t *dma_channel)
578 {
579 gdma_pair_t *pair = dma_channel->pair;
580 gdma_group_t *group = pair->group;
581 gdma_rx_channel_t *rx_chan = __containerof(dma_channel, gdma_rx_channel_t, base);
582 portENTER_CRITICAL(&pair->spinlock);
583 pair->rx_chan = NULL;
584 pair->occupy_code &= ~SEARCH_REQUEST_RX_CHANNEL;
585 portEXIT_CRITICAL(&pair->spinlock);
586
587 ESP_LOGD(TAG, "del rx channel (%d,%d)", group->group_id, pair->pair_id);
588 free(rx_chan);
589 gdma_uninstall_pair(pair);
590 return ESP_OK;
591 }
592
gdma_default_isr(void * args)593 static void IRAM_ATTR gdma_default_isr(void *args)
594 {
595 gdma_pair_t *pair = (gdma_pair_t *)args;
596 gdma_group_t *group = pair->group;
597 gdma_rx_channel_t *rx_chan = pair->rx_chan;
598 gdma_tx_channel_t *tx_chan = pair->tx_chan;
599 bool need_yield = false;
600 // clear pending interrupt event
601 uint32_t intr_status = gdma_ll_get_interrupt_status(group->hal.dev, pair->pair_id);
602 gdma_ll_clear_interrupt_status(group->hal.dev, pair->pair_id, intr_status);
603
604 if (intr_status & GDMA_LL_EVENT_RX_SUC_EOF) {
605 if (rx_chan && rx_chan->on_recv_eof) {
606 uint32_t eof_addr = gdma_ll_rx_get_success_eof_desc_addr(group->hal.dev, pair->pair_id);
607 gdma_event_data_t edata = {
608 .rx_eof_desc_addr = eof_addr
609 };
610 if (rx_chan->on_recv_eof(&rx_chan->base, &edata, rx_chan->user_data)) {
611 need_yield = true;
612 }
613 }
614 }
615
616 if (intr_status & GDMA_LL_EVENT_TX_EOF) {
617 if (tx_chan && tx_chan->on_trans_eof) {
618 uint32_t eof_addr = gdma_ll_tx_get_eof_desc_addr(group->hal.dev, pair->pair_id);
619 gdma_event_data_t edata = {
620 .tx_eof_desc_addr = eof_addr
621 };
622 if (tx_chan->on_trans_eof(&tx_chan->base, &edata, tx_chan->user_data)) {
623 need_yield = true;
624 }
625 }
626 }
627
628 if (need_yield) {
629 portYIELD_FROM_ISR();
630 }
631 }
632
gdma_install_interrupt(gdma_pair_t * pair)633 static esp_err_t gdma_install_interrupt(gdma_pair_t *pair)
634 {
635 esp_err_t ret_code = ESP_OK;
636 gdma_group_t *group = pair->group;
637 bool do_install_isr = false;
638 // pre-alloc a interrupt handle, shared with other handle, with handler disabled
639 // This is used to prevent potential concurrency between interrupt install and uninstall
640 int isr_flags = ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_INTRDISABLED;
641 intr_handle_t intr = NULL;
642 ret_code = esp_intr_alloc(gdma_periph_signals.groups[group->group_id].pairs[pair->pair_id].irq_id, isr_flags, gdma_default_isr, pair, &intr);
643 DMA_CHECK(ret_code == ESP_OK, "alloc interrupt failed", err, ret_code);
644
645 if (!pair->intr) {
646 portENTER_CRITICAL(&pair->spinlock);
647 if (!pair->intr) {
648 do_install_isr = true;
649 pair->intr = intr;
650 gdma_ll_enable_interrupt(group->hal.dev, pair->pair_id, UINT32_MAX, false); // disable all interupt events
651 gdma_ll_clear_interrupt_status(group->hal.dev, pair->pair_id, UINT32_MAX); // clear all pending events
652 }
653 portEXIT_CRITICAL(&pair->spinlock);
654 }
655 if (do_install_isr) {
656 ESP_LOGD(TAG, "install interrupt service for pair (%d,%d)", group->group_id, pair->pair_id);
657 } else {
658 // interrupt handle has been installed before, so removed this one
659 esp_intr_free(intr);
660 }
661
662 err:
663 return ret_code;
664 }
665