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->OnTxTransferDone(true, ErrorCode::OK);
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)
150 {
151 uart->HandleDmaRxDone(event_data);
152 }
153 return false;
154}
155
156// RX descriptor error requests a full RX ring recovery.
157// RX 描述符错误要求完整恢复 RX 环。
158bool IRAM_ATTR ESP32UART::DmaRxDescrErrCallback(gdma_channel_handle_t, gdma_event_data_t*,
159 void* user_data)
160{
161 auto* uart = static_cast<ESP32UART*>(user_data);
162 if (uart != nullptr)
163 {
164 uart->HandleDmaRxError();
165 }
166 return false;
167}
168
169// DMA backend bring-up does three things:
170// 1. Bind UHCI to the selected UART.
171// 2. Prepare two TX descriptor lists, one per double-buffer half.
172// 3. Prepare one circular RX descriptor ring.
173// DMA 后端初始化做三件事:
174// 1. 把 UHCI 绑定到选定 UART。
175// 2. 为双缓冲两半各准备一条 TX 描述符链。
176// 3. 准备一条循环 RX 描述符环。
177ErrorCode ESP32UART::InitDmaBackend()
178{
179 if (dma_backend_enabled_)
180 {
181 return ErrorCode::OK;
182 }
183
184 periph_module_enable(PERIPH_UHCI0_MODULE);
185 periph_module_reset(PERIPH_UHCI0_MODULE);
186
187 uhci_hal_init(&uhci_hal_, 0);
188 uhci_ll_attach_uart_port(uhci_hal_.dev, uart_num_);
189
190 uhci_seper_chr_t sep_chr = {};
191 sep_chr.sub_chr_en = 0;
192 uhci_ll_set_seper_chr(uhci_hal_.dev, &sep_chr);
193 uhci_ll_rx_set_eof_mode(uhci_hal_.dev, UHCI_RX_IDLE_EOF);
194
195 gdma_channel_alloc_config_t tx_cfg = {
196 .sibling_chan = nullptr,
197 .direction = GDMA_CHANNEL_DIRECTION_TX,
198 .flags = {},
199 };
200 if (gdma_new_ahb_channel(&tx_cfg, &tx_dma_channel_) != ESP_OK)
201 {
202 return ErrorCode::INIT_ERR;
203 }
204
205 if (gdma_connect(tx_dma_channel_, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_UHCI, 0)) !=
206 ESP_OK)
207 {
208 return ErrorCode::INIT_ERR;
209 }
210
211 gdma_transfer_config_t transfer_cfg = {
212 .max_data_burst_size = 0,
213 .access_ext_mem = true,
214 };
215 if (gdma_config_transfer(tx_dma_channel_, &transfer_cfg) != ESP_OK)
216 {
217 return ErrorCode::INIT_ERR;
218 }
219
220 size_t tx_int_alignment = 1;
221 size_t tx_ext_alignment = 1;
222 if (gdma_get_alignment_constraints(tx_dma_channel_, &tx_int_alignment,
223 &tx_ext_alignment) != ESP_OK)
224 {
225 return ErrorCode::INIT_ERR;
226 }
227 const size_t tx_dma_alignment =
228 std::max<size_t>(1, std::max(tx_int_alignment, tx_ext_alignment));
229
230 gdma_strategy_config_t tx_strategy = {
231 .owner_check = true,
232 .auto_update_desc = true,
233 .eof_till_data_popped = true,
234 };
235 if (gdma_apply_strategy(tx_dma_channel_, &tx_strategy) != ESP_OK)
236 {
237 return ErrorCode::INIT_ERR;
238 }
239
240 gdma_link_list_config_t tx_link_cfg = {
241 .num_items = 1,
242 .item_alignment = 4,
243 .flags = {},
244 };
245 gdma_link_list_handle_t tx_dma_links[2] = {nullptr, nullptr};
246
247 for (int i = 0; i < 2; ++i)
248 {
249 if (gdma_new_link_list(&tx_link_cfg, &tx_dma_links[i]) != ESP_OK)
250 {
251 return ErrorCode::INIT_ERR;
252 }
253
254 gdma_buffer_mount_config_t tx_mount = {
255 .buffer = tx_dma_buffer_.Buffer(i),
256 .buffer_alignment = tx_dma_alignment,
257 .length = 1,
258 .flags =
259 {
260 .mark_eof = 1,
261 .mark_final = 1,
262 .bypass_buffer_align_check = 0,
263 },
264 };
265
266 if (gdma_link_mount_buffers(tx_dma_links[i], 0, &tx_mount, 1, nullptr) != ESP_OK)
267 {
268 return ErrorCode::INIT_ERR;
269 }
270
271 tx_dma_head_addr_[i] = gdma_link_get_head_addr(tx_dma_links[i]);
272 if (tx_dma_head_addr_[i] == 0U)
273 {
274 return ErrorCode::INIT_ERR;
275 }
276 }
277
278 gdma_tx_event_callbacks_t tx_callbacks = {
279 .on_trans_eof = DmaTxEofCallback,
280 .on_descr_err = DmaTxDescrErrCallback,
281 };
282 if (gdma_register_tx_event_callbacks(tx_dma_channel_, &tx_callbacks, this) != ESP_OK)
283 {
284 return ErrorCode::INIT_ERR;
285 }
286
287 gdma_channel_alloc_config_t rx_cfg = {
288 .sibling_chan = nullptr,
289 .direction = GDMA_CHANNEL_DIRECTION_RX,
290 .flags = {},
291 };
292 if (gdma_new_ahb_channel(&rx_cfg, &rx_dma_channel_) != ESP_OK)
293 {
294 return ErrorCode::INIT_ERR;
295 }
296
297 if (gdma_connect(rx_dma_channel_, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_UHCI, 0)) !=
298 ESP_OK)
299 {
300 return ErrorCode::INIT_ERR;
301 }
302
303 if (gdma_config_transfer(rx_dma_channel_, &transfer_cfg) != ESP_OK)
304 {
305 return ErrorCode::INIT_ERR;
306 }
307
308 size_t rx_int_alignment = 1;
309 size_t rx_ext_alignment = 1;
310 if (gdma_get_alignment_constraints(rx_dma_channel_, &rx_int_alignment,
311 &rx_ext_alignment) != ESP_OK)
312 {
313 return ErrorCode::INIT_ERR;
314 }
315 const size_t rx_dma_alignment =
316 std::max<size_t>(1, std::max(rx_int_alignment, rx_ext_alignment));
317
318 gdma_link_list_config_t rx_link_cfg = {
319 .num_items = DMA_RX_NODE_COUNT,
320 .item_alignment = 4,
321 .flags = {},
322 };
323 if (gdma_new_link_list(&rx_link_cfg, &rx_dma_link_) != ESP_OK)
324 {
325 return ErrorCode::INIT_ERR;
326 }
327
328 // Keep one ring window reasonably large to lower ISR pressure at high baud.
329 // 保持单个环窗口适度偏大,以降低高波特率下的 ISR 压力。
330 const size_t rx_chunk_target = std::min<size_t>(
331 std::max<size_t>(32, rx_isr_buffer_size_ / DMA_RX_NODE_COUNT), 512);
332 rx_dma_chunk_size_ = std::max<size_t>(AlignUp(rx_chunk_target, 4), 32);
333 const size_t rx_storage_alignment = std::max<size_t>(4, rx_dma_alignment);
334 const size_t rx_storage_bytes =
335 AlignUp(rx_dma_chunk_size_ * DMA_RX_NODE_COUNT, rx_storage_alignment);
336
337 rx_dma_storage_ = static_cast<uint8_t*>(
338 heap_caps_aligned_alloc(rx_storage_alignment, rx_storage_bytes,
339 MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT));
340 if (rx_dma_storage_ == nullptr)
341 {
342 return ErrorCode::NO_MEM;
343 }
344
345 std::array<gdma_buffer_mount_config_t, DMA_RX_NODE_COUNT> rx_mount = {};
346 for (uint32_t i = 0; i < DMA_RX_NODE_COUNT; ++i)
347 {
348 rx_mount[i] = gdma_buffer_mount_config_t{
349 .buffer = rx_dma_storage_ + (static_cast<size_t>(i) * rx_dma_chunk_size_),
350 .buffer_alignment = rx_dma_alignment,
351 .length = rx_dma_chunk_size_,
352 .flags =
353 {
354 .mark_eof = 0,
355 .mark_final = 0,
356 .bypass_buffer_align_check = 0,
357 },
358 };
359 }
360
361 if (gdma_link_mount_buffers(rx_dma_link_, 0, rx_mount.data(), DMA_RX_NODE_COUNT,
362 nullptr) != ESP_OK)
363 {
364 return ErrorCode::INIT_ERR;
365 }
366
367 gdma_rx_event_callbacks_t rx_callbacks = {
368 .on_recv_eof = nullptr,
369 .on_descr_err = DmaRxDescrErrCallback,
370 .on_recv_done = DmaRxDoneCallback,
371 };
372 if (gdma_register_rx_event_callbacks(rx_dma_channel_, &rx_callbacks, this) != ESP_OK)
373 {
374 return ErrorCode::INIT_ERR;
375 }
376
377 if (gdma_reset(rx_dma_channel_) != ESP_OK)
378 {
379 return ErrorCode::INIT_ERR;
380 }
381
382 if (gdma_start(rx_dma_channel_, gdma_link_get_head_addr(rx_dma_link_)) != ESP_OK)
383 {
384 return ErrorCode::INIT_ERR;
385 }
386
387 rx_dma_node_index_ = 0;
388 dma_backend_enabled_ = true;
389 return ErrorCode::OK;
390}
391
392// TX DMA start only patches the dynamic fields of the pre-mounted descriptor
393// list, so the hot path avoids rebuilding descriptors for every request.
394// TX DMA 启动时只修改预挂载描述符链的动态字段,避免每次请求都重建描述符。
395bool IRAM_ATTR ESP32UART::StartDmaTx()
396{
397 if ((tx_dma_channel_ == nullptr) || !tx_active_valid_)
398 {
399 return false;
400 }
401
402 uint8_t* const active_buffer = tx_dma_buffer_.ActiveBuffer();
403 const size_t active_len = tx_active_length_;
404 if ((active_buffer == nullptr) || (active_len == 0) ||
405 (active_len > DMA_MAX_BUFFER_SIZE_PER_LINK_ITEM))
406 {
407 return false;
408 }
409
410 const int link_index = tx_dma_buffer_.ActiveBlock();
411 if ((link_index != 0) && (link_index != 1))
412 {
413 return false;
414 }
415
416 if (tx_dma_head_addr_[link_index] == 0U)
417 {
418 return false;
419 }
420
421 auto* desc = LinkItemFromHeadAddr(tx_dma_head_addr_[link_index]);
422 if (desc == nullptr)
423 {
424 return false;
425 }
426
427 // Keep descriptor list pre-mounted and only patch the dynamic transfer length in-place.
428 // 描述符链保持预挂载,只就地更新本次传输长度。
429 desc->buffer = active_buffer;
430 desc->dw0.size = static_cast<uint32_t>(active_len);
431 desc->dw0.length = static_cast<uint32_t>(active_len);
432 desc->dw0.err_eof = 0U;
433 desc->dw0.suc_eof = 1U;
434 desc->dw0.owner = GDMA_OWNER_DMA;
435 desc->next = nullptr;
436 std::atomic_thread_fence(std::memory_order_release);
437
438#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE || SOC_PSRAM_DMA_CAPABLE
439 if (!CacheSyncDmaBuffer(active_buffer, active_len, true))
440 {
441 return false;
442 }
443#endif
444
445 return gdma_start(tx_dma_channel_, tx_dma_head_addr_[link_index]) == ESP_OK;
446}
447
448// RX DMA completion can span multiple ring nodes, so consume at most one full
449// ring window per callback and advance the software node cursor in lockstep.
450// 一次 RX DMA 完成可能跨越多个环节点,因此每次回调最多消费一个完整环窗口,
451// 并同步推进软件节点游标。
452void IRAM_ATTR ESP32UART::PushDmaRxData(size_t recv_size, bool in_isr)
453{
454 if ((rx_dma_storage_ == nullptr) || (rx_dma_chunk_size_ == 0))
455 {
456 return;
457 }
458
459 const size_t max_window = rx_dma_chunk_size_ * DMA_RX_NODE_COUNT;
460 size_t remaining = std::min(recv_size, max_window);
461
462 while (remaining > 0)
463 {
464 const size_t offset = static_cast<size_t>(rx_dma_node_index_) * rx_dma_chunk_size_;
465 const size_t chunk = std::min(remaining, rx_dma_chunk_size_);
466 auto* chunk_ptr = rx_dma_storage_ + offset;
467
468#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE || SOC_PSRAM_DMA_CAPABLE
469 if (!CacheSyncDmaBuffer(chunk_ptr, chunk, false))
470 {
471 HandleDmaRxError();
472 return;
473 }
474#endif
475 PushRxBytes(chunk_ptr, chunk, in_isr);
476 remaining -= chunk;
477 rx_dma_node_index_ = (rx_dma_node_index_ + 1U) % DMA_RX_NODE_COUNT;
478 }
479}
480
481// RX DMA completion either reports a full node or the final EOF-sized tail.
482// RX DMA 完成要么报告整节点,要么报告带 EOF 的尾段长度。
483void IRAM_ATTR ESP32UART::HandleDmaRxDone(gdma_event_data_t* event_data)
484{
485 if ((rx_dma_storage_ == nullptr) || (rx_dma_chunk_size_ == 0))
486 {
487 return;
488 }
489
490 if ((event_data != nullptr) && event_data->flags.abnormal_eof)
491 {
492 HandleDmaRxError();
493 return;
494 }
495
496 size_t recv_size = rx_dma_chunk_size_;
497 if ((event_data != nullptr) && event_data->flags.normal_eof)
498 {
499 const size_t eof_size = gdma_link_count_buffer_size_till_eof(
500 rx_dma_link_, static_cast<int>(rx_dma_node_index_));
501 if (eof_size > 0)
502 {
503 recv_size = eof_size;
504 }
505 }
506
507 PushDmaRxData(recv_size, true);
508}
509
510// RX DMA recovery restarts the circular ring from node zero.
511// RX DMA 恢复会从节点零重新启动整个环。
512void IRAM_ATTR ESP32UART::HandleDmaRxError()
513{
514 if ((rx_dma_channel_ == nullptr) || (rx_dma_link_ == nullptr))
515 {
516 return;
517 }
518
519 gdma_stop(rx_dma_channel_);
520 gdma_reset(rx_dma_channel_);
521 rx_dma_node_index_ = 0;
522 (void)gdma_start(rx_dma_channel_, gdma_link_get_head_addr(rx_dma_link_));
523}
524
525// TX DMA recovery aborts the current hardware transfer and lets the common TX
526// completion path clean up the software state.
527// TX DMA 恢复会中止当前硬件传输,再交给公共 TX 完成路径清理软件状态。
528void IRAM_ATTR ESP32UART::HandleDmaTxError()
529{
530 if (tx_dma_channel_ != nullptr)
531 {
532 gdma_stop(tx_dma_channel_);
533 gdma_reset(tx_dma_channel_);
534 }
536}
537
538} // namespace LibXR
539
540#endif
int ActiveBlock() const
获取当前活动缓冲区编号 Returns the current active block index
uint8_t * Buffer(int block) const
获取指定编号缓冲区的指针 Returns the pointer of the specified block
uint8_t * ActiveBuffer() const
获取当前正在使用的缓冲区指针 Returns the currently active buffer
size_t rx_isr_buffer_size_
Size of rx_isr_buffer_.
Definition esp_uart.hpp:357
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
bool tx_active_valid_
Whether the active TX metadata is valid.
Definition esp_uart.hpp:364
void OnTxTransferDone(bool in_isr, ErrorCode result)
Finalize one TX transfer result.
Definition esp_uart.cpp:807
DoubleBuffer tx_dma_buffer_
TX double-buffer view for the DMA path.
Definition esp_uart.hpp:360
uart_port_t uart_num_
Selected UART peripheral index.
Definition esp_uart.hpp:348
void PushRxBytes(const uint8_t *data, size_t size, bool in_isr)
Push RX bytes into the software queue.
Definition esp_uart.cpp:771
size_t tx_active_length_
Active TX payload length in bytes.
Definition esp_uart.hpp:362
LibXR 命名空间
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举
@ INIT_ERR
初始化错误 | Initialization error
@ NO_MEM
内存不足 | Insufficient memory
@ FAILED
操作失败 | Operation failed
@ OK
操作成功 | Operation successful