libxr  1.0
Want to be the best embedded framework
Loading...
Searching...
No Matches
esp_uart_dma.cpp
1#include "esp_uart.hpp"
2
3#if SOC_GDMA_SUPPORTED && SOC_UHCI_SUPPORTED
4
5#include <algorithm>
6#include <array>
7
8#include "esp_attr.h"
9#include "esp_err.h"
10#include "esp_heap_caps.h"
11#include "esp_memory_utils.h"
12#include "esp_private/periph_ctrl.h"
13#include "hal/uhci_ll.h"
14#include "soc/ext_mem_defs.h"
15
16namespace
17{
18// RX uses a circular DMA descriptor ring, similar to STM/CH circular RX DMA
19// behavior (continuous receive + software consumer index).
20// RX 使用循环 DMA 描述符环,语义接近 STM/CH 的循环 RX DMA:持续接收,
21// 软件侧维护消费索引。
22constexpr uint32_t DMA_RX_NODE_COUNT = 8;
23
24// Current ESP GDMA link items cannot describe more than 4095 bytes in one node.
25// 当前 ESP GDMA link item 单节点最多只能描述 4095 字节。
26constexpr size_t DMA_MAX_BUFFER_SIZE_PER_LINK_ITEM = 4095U;
27
28// Minimal local view of the GDMA link descriptor layout used for in-place patching.
29// 为就地修改描述符长度而保留的 GDMA link descriptor 最小本地视图。
30struct GdmaLinkItem
31{
32 struct
33 {
34 uint32_t size : 12;
35 uint32_t length : 12;
36 uint32_t reserved24 : 4;
37 uint32_t err_eof : 1;
38 uint32_t reserved29 : 1;
39 uint32_t suc_eof : 1;
40 uint32_t owner : 1;
41 } dw0;
42 void* buffer;
43 GdmaLinkItem* next;
44};
45
46constexpr uint32_t GDMA_OWNER_CPU = 0U;
47constexpr uint32_t GDMA_OWNER_DMA = 1U;
48
49// Helper used for DMA storage and node-size alignment calculations.
50// 用于 DMA storage 和 node 大小对齐计算的辅助函数。
51size_t AlignUp(size_t value, size_t align)
52{
53 if (align <= 1)
54 {
55 return value;
56 }
57 return ((value + align - 1) / align) * align;
58}
59
60// Convert the cached address returned by ESP-IDF into the non-cache alias used
61// by the GDMA descriptors when the target SoC exposes one.
62// 当目标 SoC 提供 non-cache alias 时,把 ESP-IDF 返回的 cache 地址转换成
63// GDMA 描述符使用的 non-cache 地址。
64uintptr_t CacheAddrToNonCache(uintptr_t addr)
65{
66#if SOC_NON_CACHEABLE_OFFSET
67 return addr + SOC_NON_CACHEABLE_OFFSET;
68#else
69 return addr;
70#endif
71}
72
73// Recover the first link item from a GDMA list head address.
74// 从 GDMA list head 地址恢复首个 link item。
75GdmaLinkItem* LinkItemFromHeadAddr(uintptr_t head_addr)
76{
77 return reinterpret_cast<GdmaLinkItem*>(CacheAddrToNonCache(head_addr));
78}
79
80#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE || SOC_PSRAM_DMA_CAPABLE
81extern "C" esp_err_t esp_cache_msync(void* addr, size_t size, int flags);
82
83constexpr int CACHE_SYNC_FLAG_UNALIGNED = (1 << 1);
84constexpr int CACHE_SYNC_FLAG_DIR_C2M = (1 << 2);
85constexpr int CACHE_SYNC_FLAG_DIR_M2C = (1 << 3);
86
87// Synchronize one DMA window when the active memory region is cacheable.
88// 当当前内存区域可缓存时,同步一个 DMA 窗口。
89bool CacheSyncDmaBuffer(const void* addr, size_t size, bool cache_to_mem)
90{
91 if ((addr == nullptr) || (size == 0U))
92 {
93 return true;
94 }
95
96#if SOC_PSRAM_DMA_CAPABLE && !SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
97 if (!esp_ptr_external_ram(addr))
98 {
99 return true;
100 }
101#endif
102
103 int flags = cache_to_mem ? CACHE_SYNC_FLAG_DIR_C2M : CACHE_SYNC_FLAG_DIR_M2C;
104 flags |= CACHE_SYNC_FLAG_UNALIGNED;
105
106 const esp_err_t ret = esp_cache_msync(const_cast<void*>(addr), size, flags);
107 // Non-cacheable regions can return ESP_ERR_INVALID_ARG; treat as no-op success.
108 return (ret == ESP_OK) || (ret == ESP_ERR_INVALID_ARG);
109}
110#endif
111} // namespace
112
113namespace LibXR
114{
115
116// TX EOF means the current staged active payload has fully left the DMA engine.
117// TX EOF 表示当前暂存的 active payload 已经完整离开 DMA 引擎。
118bool IRAM_ATTR ESP32UART::DmaTxEofCallback(gdma_channel_handle_t, gdma_event_data_t*,
119 void* user_data)
120{
121 auto* uart = static_cast<ESP32UART*>(user_data);
122 if (uart != nullptr)
123 {
124 uart->tx_dma_model_.OnTransferDone(true);
125 }
126 return false;
127}
128
129// TX descriptor error is surfaced as a backend TX failure.
130// TX 描述符错误会被上报为后端 TX 失败。
131bool IRAM_ATTR ESP32UART::DmaTxDescrErrCallback(gdma_channel_handle_t, gdma_event_data_t*,
132 void* user_data)
133{
134 auto* uart = static_cast<ESP32UART*>(user_data);
135 if (uart != nullptr)
136 {
137 uart->HandleDmaTxError();
138 }
139 return false;
140}
141
142// RX done callback only forwards the event into the UART object state machine.
143// RX 完成回调只负责把事件转发到 UART 对象状态机。
144bool IRAM_ATTR ESP32UART::DmaRxDoneCallback(gdma_channel_handle_t,
145 gdma_event_data_t* event_data,
146 void* user_data)
147{
148 auto* uart = static_cast<ESP32UART*>(user_data);
149 if ((uart != nullptr) && uart->rx_config_gate_.TryEnterRx())
150 {
151 uart->HandleDmaRxDone(event_data);
152 if (uart->rx_config_gate_.LeaveRx())
153 {
154 uart->tx_dma_model_.RequestConfig(true);
155 }
156 }
157 return false;
158}
159
160// RX descriptor error requests a full RX ring recovery.
161// RX 描述符错误要求完整恢复 RX 环。
162bool IRAM_ATTR ESP32UART::DmaRxDescrErrCallback(gdma_channel_handle_t, gdma_event_data_t*,
163 void* user_data)
164{
165 auto* uart = static_cast<ESP32UART*>(user_data);
166 if ((uart != nullptr) && uart->rx_config_gate_.TryEnterRx())
167 {
168 uart->HandleDmaRxError();
169 if (uart->rx_config_gate_.LeaveRx())
170 {
171 uart->tx_dma_model_.RequestConfig(true);
172 }
173 }
174 return false;
175}
176
177// DMA backend bring-up does three things:
178// 1. Bind UHCI to the selected UART.
179// 2. Prepare two TX descriptor lists, one per double-buffer half.
180// 3. Prepare one circular RX descriptor ring.
181// DMA 后端初始化做三件事:
182// 1. 把 UHCI 绑定到选定 UART。
183// 2. 为双缓冲两半各准备一条 TX 描述符链。
184// 3. 准备一条循环 RX 描述符环。
185ErrorCode ESP32UART::InitDmaBackend()
186{
187 if (dma_backend_enabled_)
188 {
189 return ErrorCode::OK;
190 }
191
192 periph_module_enable(PERIPH_UHCI0_MODULE);
193 periph_module_reset(PERIPH_UHCI0_MODULE);
194
195 uhci_hal_init(&uhci_hal_, 0);
196 uhci_ll_attach_uart_port(uhci_hal_.dev, uart_num_);
197
198 uhci_seper_chr_t sep_chr = {};
199 sep_chr.sub_chr_en = 0;
200 uhci_ll_set_seper_chr(uhci_hal_.dev, &sep_chr);
201 uhci_ll_rx_set_eof_mode(uhci_hal_.dev, UHCI_RX_IDLE_EOF);
202
203 gdma_channel_alloc_config_t tx_cfg = {
204 .sibling_chan = nullptr,
205 .direction = GDMA_CHANNEL_DIRECTION_TX,
206 .flags = {},
207 };
208 if (gdma_new_ahb_channel(&tx_cfg, &tx_dma_channel_) != ESP_OK)
209 {
210 return ErrorCode::INIT_ERR;
211 }
212
213 if (gdma_connect(tx_dma_channel_, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_UHCI, 0)) !=
214 ESP_OK)
215 {
216 return ErrorCode::INIT_ERR;
217 }
218
219 gdma_transfer_config_t transfer_cfg = {
220 .max_data_burst_size = 0,
221 .access_ext_mem = true,
222 };
223 if (gdma_config_transfer(tx_dma_channel_, &transfer_cfg) != ESP_OK)
224 {
225 return ErrorCode::INIT_ERR;
226 }
227
228 size_t tx_int_alignment = 1;
229 size_t tx_ext_alignment = 1;
230 if (gdma_get_alignment_constraints(tx_dma_channel_, &tx_int_alignment,
231 &tx_ext_alignment) != ESP_OK)
232 {
233 return ErrorCode::INIT_ERR;
234 }
235 const size_t tx_dma_alignment =
236 std::max<size_t>(1, std::max(tx_int_alignment, tx_ext_alignment));
237
238 gdma_strategy_config_t tx_strategy = {
239 .owner_check = true,
240 .auto_update_desc = true,
241 .eof_till_data_popped = true,
242 };
243 if (gdma_apply_strategy(tx_dma_channel_, &tx_strategy) != ESP_OK)
244 {
245 return ErrorCode::INIT_ERR;
246 }
247
248 gdma_link_list_config_t tx_link_cfg = {
249 .num_items = 1,
250 .item_alignment = 4,
251 .flags = {},
252 };
253 gdma_link_list_handle_t tx_dma_links[2] = {nullptr, nullptr};
254
255 for (int i = 0; i < 2; ++i)
256 {
257 if (gdma_new_link_list(&tx_link_cfg, &tx_dma_links[i]) != ESP_OK)
258 {
259 return ErrorCode::INIT_ERR;
260 }
261
262 gdma_buffer_mount_config_t tx_mount = {
263 .buffer = tx_dma_model_.Buffer(i),
264 .buffer_alignment = tx_dma_alignment,
265 .length = 1,
266 .flags =
267 {
268 .mark_eof = 1,
269 .mark_final = 1,
270 .bypass_buffer_align_check = 0,
271 },
272 };
273
274 if (gdma_link_mount_buffers(tx_dma_links[i], 0, &tx_mount, 1, nullptr) != ESP_OK)
275 {
276 return ErrorCode::INIT_ERR;
277 }
278
279 tx_dma_head_addr_[i] = gdma_link_get_head_addr(tx_dma_links[i]);
280 if (tx_dma_head_addr_[i] == 0U)
281 {
282 return ErrorCode::INIT_ERR;
283 }
284 }
285
286 gdma_tx_event_callbacks_t tx_callbacks = {
287 .on_trans_eof = DmaTxEofCallback,
288 .on_descr_err = DmaTxDescrErrCallback,
289 };
290 if (gdma_register_tx_event_callbacks(tx_dma_channel_, &tx_callbacks, this) != ESP_OK)
291 {
292 return ErrorCode::INIT_ERR;
293 }
294
295 gdma_channel_alloc_config_t rx_cfg = {
296 .sibling_chan = nullptr,
297 .direction = GDMA_CHANNEL_DIRECTION_RX,
298 .flags = {},
299 };
300 if (gdma_new_ahb_channel(&rx_cfg, &rx_dma_channel_) != ESP_OK)
301 {
302 return ErrorCode::INIT_ERR;
303 }
304
305 if (gdma_connect(rx_dma_channel_, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_UHCI, 0)) !=
306 ESP_OK)
307 {
308 return ErrorCode::INIT_ERR;
309 }
310
311 if (gdma_config_transfer(rx_dma_channel_, &transfer_cfg) != ESP_OK)
312 {
313 return ErrorCode::INIT_ERR;
314 }
315
316 size_t rx_int_alignment = 1;
317 size_t rx_ext_alignment = 1;
318 if (gdma_get_alignment_constraints(rx_dma_channel_, &rx_int_alignment,
319 &rx_ext_alignment) != ESP_OK)
320 {
321 return ErrorCode::INIT_ERR;
322 }
323 const size_t rx_dma_alignment =
324 std::max<size_t>(1, std::max(rx_int_alignment, rx_ext_alignment));
325
326 gdma_link_list_config_t rx_link_cfg = {
327 .num_items = DMA_RX_NODE_COUNT,
328 .item_alignment = 4,
329 .flags = {},
330 };
331 if (gdma_new_link_list(&rx_link_cfg, &rx_dma_link_) != ESP_OK)
332 {
333 return ErrorCode::INIT_ERR;
334 }
335
336 // Keep one ring window reasonably large to lower ISR pressure at high baud.
337 // 保持单个环窗口适度偏大,以降低高波特率下的 ISR 压力。
338 const size_t rx_chunk_target = std::min<size_t>(
339 std::max<size_t>(32, rx_isr_buffer_size_ / DMA_RX_NODE_COUNT), 512);
340 rx_dma_chunk_size_ = std::max<size_t>(AlignUp(rx_chunk_target, 4), 32);
341 const size_t rx_storage_alignment = std::max<size_t>(4, rx_dma_alignment);
342 const size_t rx_storage_bytes =
343 AlignUp(rx_dma_chunk_size_ * DMA_RX_NODE_COUNT, rx_storage_alignment);
344
345 rx_dma_storage_ = static_cast<uint8_t*>(
346 heap_caps_aligned_alloc(rx_storage_alignment, rx_storage_bytes,
347 MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT));
348 if (rx_dma_storage_ == nullptr)
349 {
350 return ErrorCode::NO_MEM;
351 }
352
353 std::array<gdma_buffer_mount_config_t, DMA_RX_NODE_COUNT> rx_mount = {};
354 for (uint32_t i = 0; i < DMA_RX_NODE_COUNT; ++i)
355 {
356 rx_mount[i] = gdma_buffer_mount_config_t{
357 .buffer = rx_dma_storage_ + (static_cast<size_t>(i) * rx_dma_chunk_size_),
358 .buffer_alignment = rx_dma_alignment,
359 .length = rx_dma_chunk_size_,
360 .flags =
361 {
362 .mark_eof = 0,
363 .mark_final = 0,
364 .bypass_buffer_align_check = 0,
365 },
366 };
367 }
368
369 if (gdma_link_mount_buffers(rx_dma_link_, 0, rx_mount.data(), DMA_RX_NODE_COUNT,
370 nullptr) != ESP_OK)
371 {
372 return ErrorCode::INIT_ERR;
373 }
374
375 gdma_rx_event_callbacks_t rx_callbacks = {
376 .on_recv_eof = nullptr,
377 .on_descr_err = DmaRxDescrErrCallback,
378 .on_recv_done = DmaRxDoneCallback,
379 };
380 if (gdma_register_rx_event_callbacks(rx_dma_channel_, &rx_callbacks, this) != ESP_OK)
381 {
382 return ErrorCode::INIT_ERR;
383 }
384
385 if (gdma_reset(rx_dma_channel_) != ESP_OK)
386 {
387 return ErrorCode::INIT_ERR;
388 }
389
390 if (gdma_start(rx_dma_channel_, gdma_link_get_head_addr(rx_dma_link_)) != ESP_OK)
391 {
392 return ErrorCode::INIT_ERR;
393 }
394
395 rx_dma_node_index_ = 0;
396 dma_backend_enabled_ = true;
397 return ErrorCode::OK;
398}
399
400// TX DMA start only patches the dynamic fields of the pre-mounted descriptor
401// list, so the hot path avoids rebuilding descriptors for every request.
402// TX DMA 启动时只修改预挂载描述符链的动态字段,避免每次请求都重建描述符。
403bool IRAM_ATTR ESP32UART::StartDmaTx(uint8_t* data, size_t size, int block)
404{
405 if ((tx_dma_channel_ == nullptr) || (data == nullptr) || (size == 0U) ||
406 (size > DMA_MAX_BUFFER_SIZE_PER_LINK_ITEM) || ((block != 0) && (block != 1)) ||
407 (tx_dma_head_addr_[block] == 0U))
408 {
409 return false;
410 }
411
412 auto* desc = LinkItemFromHeadAddr(tx_dma_head_addr_[block]);
413 if (desc == nullptr)
414 {
415 return false;
416 }
417
418 desc->buffer = data;
419 desc->dw0.size = static_cast<uint32_t>(size);
420 desc->dw0.length = static_cast<uint32_t>(size);
421 desc->dw0.err_eof = 0U;
422 desc->dw0.suc_eof = 1U;
423 desc->dw0.owner = GDMA_OWNER_DMA;
424 desc->next = nullptr;
425 std::atomic_thread_fence(std::memory_order_release);
426
427#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE || SOC_PSRAM_DMA_CAPABLE
428 if (!CacheSyncDmaBuffer(data, size, true))
429 {
430 return false;
431 }
432#endif
433
434 return gdma_start(tx_dma_channel_, tx_dma_head_addr_[block]) == ESP_OK;
435}
436
437// RX DMA completion can span multiple ring nodes, so consume at most one full
438// ring window per callback and advance the software node cursor in lockstep.
439// 一次 RX DMA 完成可能跨越多个环节点,因此每次回调最多消费一个完整环窗口,
440// 并同步推进软件节点游标。
441void IRAM_ATTR ESP32UART::PushDmaRxData(size_t recv_size, bool in_isr)
442{
443 if ((rx_dma_storage_ == nullptr) || (rx_dma_chunk_size_ == 0))
444 {
445 return;
446 }
447
448 const size_t max_window = rx_dma_chunk_size_ * DMA_RX_NODE_COUNT;
449 size_t remaining = std::min(recv_size, max_window);
450
451 while (remaining > 0)
452 {
453 const size_t offset = static_cast<size_t>(rx_dma_node_index_) * rx_dma_chunk_size_;
454 const size_t chunk = std::min(remaining, rx_dma_chunk_size_);
455 auto* chunk_ptr = rx_dma_storage_ + offset;
456
457#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE || SOC_PSRAM_DMA_CAPABLE
458 if (!CacheSyncDmaBuffer(chunk_ptr, chunk, false))
459 {
460 HandleDmaRxError();
461 return;
462 }
463#endif
464 PushRxBytes(chunk_ptr, chunk, in_isr);
465 remaining -= chunk;
466 rx_dma_node_index_ = (rx_dma_node_index_ + 1U) % DMA_RX_NODE_COUNT;
467 }
468}
469
470// RX DMA completion either reports a full node or the final EOF-sized tail.
471// RX DMA 完成要么报告整节点,要么报告带 EOF 的尾段长度。
472void IRAM_ATTR ESP32UART::HandleDmaRxDone(gdma_event_data_t* event_data)
473{
474 if ((rx_dma_storage_ == nullptr) || (rx_dma_chunk_size_ == 0))
475 {
476 return;
477 }
478
479 if ((event_data != nullptr) && event_data->flags.abnormal_eof)
480 {
481 HandleDmaRxError();
482 return;
483 }
484
485 size_t recv_size = rx_dma_chunk_size_;
486 if ((event_data != nullptr) && event_data->flags.normal_eof)
487 {
488 const size_t eof_size = gdma_link_count_buffer_size_till_eof(
489 rx_dma_link_, static_cast<int>(rx_dma_node_index_));
490 if (eof_size > 0)
491 {
492 recv_size = eof_size;
493 }
494 }
495
496 PushDmaRxData(recv_size, true);
497}
498
499// RX DMA recovery restarts the circular ring from node zero.
500// RX DMA 恢复会从节点零重新启动整个环。
501void IRAM_ATTR ESP32UART::HandleDmaRxError()
502{
503 if ((rx_dma_channel_ == nullptr) || (rx_dma_link_ == nullptr))
504 {
505 return;
506 }
507
508 gdma_stop(rx_dma_channel_);
509 gdma_reset(rx_dma_channel_);
510 rx_dma_node_index_ = 0;
511 (void)gdma_start(rx_dma_channel_, gdma_link_get_head_addr(rx_dma_link_));
512}
513
514// TX DMA recovery aborts the current hardware transfer before the common terminal
515// path advances a ready pending request.
516// TX DMA 恢复先中止当前硬件传输,再由公共终止路径推进 ready pending 请求。
517void IRAM_ATTR ESP32UART::HandleDmaTxError()
518{
519 if (tx_dma_channel_ != nullptr)
520 {
521 gdma_stop(tx_dma_channel_);
522 gdma_reset(tx_dma_channel_);
523 }
524 tx_dma_model_.OnTransferError(true);
525}
526
527} // namespace LibXR
528
529#endif
size_t rx_isr_buffer_size_
Size of rx_isr_buffer_.
Definition esp_uart.hpp:345
ESP32UART(uart_port_t uart_num, int tx_pin, int rx_pin, int rts_pin=PIN_NO_CHANGE, int cts_pin=PIN_NO_CHANGE, size_t rx_buffer_size=1024, size_t tx_buffer_size=512, uint32_t tx_queue_size=5, UART::Configuration config={115200, UART::Parity::NO_PARITY, 8, 1}, bool enable_dma=true)
Create and initialize one ESP32 UART instance.
Definition esp_uart.cpp:104
UartDmaTxModel< ESP32UART > tx_dma_model_
One-shot DMA TX execution model.
Definition esp_uart.hpp:363
uart_port_t uart_num_
Selected UART peripheral index.
Definition esp_uart.hpp:334
void PushRxBytes(const uint8_t *data, size_t size, bool in_isr)
Push RX bytes into the software queue.
Definition esp_uart.cpp:650
LibXR 命名空间
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举
@ INIT_ERR
初始化错误 | Initialization error
@ NO_MEM
内存不足 | Insufficient memory
@ OK
操作成功 | Operation successful