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