libxr  1.0
Want to be the best embedded framework
Loading...
Searching...
No Matches
esp_cdc_jtag.cpp
1#include "esp_cdc_jtag.hpp"
2
3#if SOC_USB_SERIAL_JTAG_SUPPORTED && \
4 ((defined(CONFIG_IDF_TARGET_ESP32C3) && CONFIG_IDF_TARGET_ESP32C3) || \
5 (defined(CONFIG_IDF_TARGET_ESP32C6) && CONFIG_IDF_TARGET_ESP32C6))
6
7#include <algorithm>
8#include <cstring>
9
10#include "esp_attr.h"
11#include "hal/usb_serial_jtag_ll.h"
12#include "soc/interrupts.h"
13
14namespace
15{
16// TX 中断源 / TX interrupt source.
17constexpr uint32_t TX_INTR_MASK = USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY;
18
19// RX 中断源 / RX interrupt source.
20constexpr uint32_t RX_INTR_MASK = USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT;
21
22// 本后端使用的完整中断集合 / Full interrupt set used by this backend.
23constexpr uint32_t ALL_INTR_MASK = TX_INTR_MASK | RX_INTR_MASK;
24} // namespace
25
26namespace LibXR
27{
28
29void ESP32CDCJtagReadPort::OnRxDequeue(bool in_isr) { owner_.DrainRxToQueue(in_isr); }
30
31// 先构造队列桥,再绑定 TX helper storage / Bind queue bridges before TX helper storage.
32ESP32CDCJtag::ESP32CDCJtag(size_t rx_buffer_size, size_t tx_buffer_size,
33 uint32_t tx_queue_size, UART::Configuration config)
34 : UART(&_read_port, &_write_port),
35 config_(config),
36 tx_slot_storage_(new uint8_t[tx_buffer_size * 2U]),
37 _read_port(rx_buffer_size, *this),
38 _write_port(tx_queue_size, tx_buffer_size)
39{
40 ASSERT(tx_buffer_size > 0U);
41
42 tx_double_buffer_.Init({tx_slot_storage_, tx_buffer_size * 2U});
43
44 _read_port = ReadFun;
45 _write_port = WriteFun;
46
47 if (SetConfig(config_) != ErrorCode::OK)
48 {
49 ASSERT(false);
50 return;
51 }
52
53 if (InitHardware() != ErrorCode::OK)
54 {
55 ASSERT(false);
56 }
57}
58
59// USB Serial/JTAG 只支持固定 8N1 / USB Serial/JTAG only supports fixed 8N1.
60ErrorCode ESP32CDCJtag::SetConfig(UART::Configuration config)
61{
62 if ((config.data_bits != 8) || (config.stop_bits != 1) ||
63 (config.parity != UART::Parity::NO_PARITY))
64 {
65 return ErrorCode::ARG_ERR;
66 }
67 config_ = config;
68 return ErrorCode::OK;
69}
70
71// 安装 IRAM 中断并保持 RX 使能 / Install IRAM interrupt and keep RX armed.
72ErrorCode ESP32CDCJtag::InitHardware()
73{
74 if (hw_inited_)
75 {
76 return ErrorCode::OK;
77 }
78
79 ResetTxState(false);
80
81 const esp_err_t intr_ans = esp_intr_alloc(
82 ETS_USB_SERIAL_JTAG_INTR_SOURCE, ESP_INTR_FLAG_IRAM, IsrEntry, this, &intr_handle_);
83 intr_installed_ = (intr_ans == ESP_OK);
84 if (!intr_installed_)
85 {
86 intr_handle_ = nullptr;
87 return ErrorCode::INIT_ERR;
88 }
89
90 usb_serial_jtag_ll_clr_intsts_mask(ALL_INTR_MASK);
91 usb_serial_jtag_ll_ena_intr_mask(RX_INTR_MASK);
92
93 hw_inited_ = true;
94 return ErrorCode::OK;
95}
96
97// ISR 跳板函数 / ISR trampoline.
98void IRAM_ATTR ESP32CDCJtag::IsrEntry(void* arg)
99{
100 auto* cdc = static_cast<ESP32CDCJtag*>(arg);
101 if (cdc != nullptr)
102 {
103 cdc->HandleInterrupt();
104 }
105}
106
107// WritePort 跳板函数 / WritePort trampoline.
108ErrorCode IRAM_ATTR ESP32CDCJtag::WriteFun(WritePort& port, bool in_isr)
109{
110 auto* cdc = LibXR::ContainerOf(&port, &ESP32CDCJtag::_write_port);
111 return cdc->TryStartTx(in_isr);
112}
113
114// RX 走中断驱动 / RX is interrupt-driven.
115ErrorCode ESP32CDCJtag::ReadFun(ReadPort&, bool) { return ErrorCode::PENDING; }
116
117// 清除 active TX 状态 / Clear active TX state.
118void IRAM_ATTR ESP32CDCJtag::ClearActiveTx() { tx_double_buffer_.ClearActive(); }
119
120// 清除 pending TX 状态 / Clear pending TX state.
121void IRAM_ATTR ESP32CDCJtag::ClearPendingTx() { tx_double_buffer_.ClearPending(); }
122
123// 重置两个 TX slot 和忙标志 / Reset TX slots and busy flag.
124void IRAM_ATTR ESP32CDCJtag::ResetTxState(bool)
125{
126 tx_double_buffer_.Reset();
127 tx_busy_.Clear();
128}
129
130// 队列元数据和 payload 仍然分离 / Queue metadata and payload stay split.
131bool IRAM_ATTR ESP32CDCJtag::DequeueTxToSlot(uint8_t* slot, size_t& size,
132 WriteInfoBlock& info, bool in_isr)
133{
134 (void)in_isr;
135
136 WriteInfoBlock peek_info = {};
137 if (write_port_->queue_info_->Peek(peek_info) != ErrorCode::OK)
138 {
139 return false;
140 }
141
142 if (peek_info.data.size_ > tx_double_buffer_.BufferSize())
143 {
144 ASSERT(false);
145 return false;
146 }
147
148 if (write_port_->queue_data_->PopBatch(slot, peek_info.data.size_) != ErrorCode::OK)
149 {
150 return false;
151 }
152
153 if (write_port_->queue_info_->Pop(info) != ErrorCode::OK)
154 {
155 return false;
156 }
157
158 size = peek_info.data.size_;
159 return true;
160}
161
162// 装载 active TX / Load active TX.
163bool IRAM_ATTR ESP32CDCJtag::LoadActiveTxFromQueue(bool in_isr)
164{
165 if (tx_double_buffer_.HasActive())
166 {
167 return true;
168 }
169
170 size_t active_size = 0U;
171 WriteInfoBlock active_info = {};
172 if (!DequeueTxToSlot(tx_double_buffer_.ActiveBuffer(), active_size, active_info,
173 in_isr))
174 {
175 return false;
176 }
177
178 tx_double_buffer_.LoadActive(active_size, active_info);
179 return true;
180}
181
182// 装载 pending TX / Load pending TX.
183bool IRAM_ATTR ESP32CDCJtag::LoadPendingTxFromQueue(bool in_isr)
184{
185 if (tx_double_buffer_.HasPending())
186 {
187 return false;
188 }
189
190 size_t pending_size = 0U;
191 WriteInfoBlock pending_info = {};
192 if (!DequeueTxToSlot(tx_double_buffer_.PendingBuffer(), pending_size, pending_info,
193 in_isr))
194 {
195 return false;
196 }
197
198 tx_double_buffer_.LoadPending(pending_size, pending_info);
199 return true;
200}
201
202// 硬件空闲时提升 pending TX / Promote pending TX when hardware is idle.
203bool IRAM_ATTR ESP32CDCJtag::StartPendingTxIfIdle(bool in_isr)
204{
205 if (tx_busy_.IsSet() || tx_double_buffer_.HasActive() ||
206 !tx_double_buffer_.HasPending())
207 {
208 return false;
209 }
210
211 if (!tx_double_buffer_.PromotePending())
212 {
213 return false;
214 }
215
216 return StartAndReportActive(in_isr);
217}
218
219// 向硬件 TX FIFO 补料 / Pump hardware TX FIFO.
220bool IRAM_ATTR ESP32CDCJtag::PumpTx(bool)
221{
222 while (tx_busy_.IsSet())
223 {
224 if (!tx_double_buffer_.HasActive() || (tx_double_buffer_.ActiveBuffer() == nullptr) ||
225 (tx_double_buffer_.ActiveOffset() >= tx_double_buffer_.ActiveLength()))
226 {
227 tx_busy_.Clear();
228 return true;
229 }
230
231 const size_t active_offset = tx_double_buffer_.ActiveOffset();
232 const uint32_t remain =
233 static_cast<uint32_t>(tx_double_buffer_.ActiveLength() - active_offset);
234 const int written = usb_serial_jtag_ll_write_txfifo(
235 tx_double_buffer_.ActiveBuffer() + active_offset, remain);
236 if (written <= 0)
237 {
238 return false;
239 }
240
241 tx_double_buffer_.SetActiveOffset(active_offset + static_cast<size_t>(written));
242 if (tx_double_buffer_.ActiveOffset() >= tx_double_buffer_.ActiveLength())
243 {
244 tx_busy_.Clear();
245 return true;
246 }
247 }
248
249 return false;
250}
251
252// 尽量把 RX 字节推进软件队列 / Push RX bytes into software queue.
253void IRAM_ATTR ESP32CDCJtag::PushRxBytes(const uint8_t* data, size_t size, bool in_isr)
254{
255 size_t offset = 0U;
256 bool pushed_any = false;
257
258 while (offset < size)
259 {
260 const size_t free_space = read_port_->queue_data_->EmptySize();
261 if (free_space == 0U)
262 {
263 break;
264 }
265
266 const size_t chunk = std::min(free_space, size - offset);
267 if (read_port_->queue_data_->PushBatch(data + offset, chunk) != ErrorCode::OK)
268 {
269 break;
270 }
271
272 offset += chunk;
273 pushed_any = true;
274 }
275
276 if (pushed_any)
277 {
278 read_port_->ProcessPendingReads(in_isr);
279 }
280}
281
282// RX 排空由 IRQ 和出队回调共享 / RX drain is shared by IRQ and dequeue callback.
283// atomic gate 防止并发消费同一个 FIFO / Atomic gate prevents concurrent FIFO consumption.
284void IRAM_ATTR ESP32CDCJtag::DrainRxToQueue(bool in_isr)
285{
286 if (rx_draining_.TestAndSet())
287 {
288 return;
289 }
290
291 while (usb_serial_jtag_ll_rxfifo_data_available())
292 {
293 uint8_t rx_tmp[64] = {};
294 const int got = usb_serial_jtag_ll_read_rxfifo(rx_tmp, sizeof(rx_tmp));
295 if (got <= 0)
296 {
297 break;
298 }
299
300 PushRxBytes(rx_tmp, static_cast<size_t>(got), in_isr);
301
302 if (read_port_->queue_data_->EmptySize() == 0U)
303 {
304 break;
305 }
306 }
307
308 rx_draining_.Clear();
309}
310
311// 使能 TX 中断并尝试补料 / Arm TX interrupt and try one pump.
312bool IRAM_ATTR ESP32CDCJtag::StartActiveTransfer(bool in_isr)
313{
314 if (!tx_double_buffer_.HasActive() || (tx_double_buffer_.ActiveBuffer() == nullptr) ||
315 (tx_double_buffer_.ActiveLength() == 0U))
316 {
317 return false;
318 }
319
320 if (tx_busy_.TestAndSet())
321 {
322 return true;
323 }
324
325 tx_double_buffer_.SetActiveOffset(0U);
326 usb_serial_jtag_ll_ena_intr_mask(TX_INTR_MASK);
327 (void)PumpTx(in_isr);
328 return true;
329}
330
331// 将 TX 交给硬件后再上报完成 / Report completion after handing TX to hardware.
332bool IRAM_ATTR ESP32CDCJtag::StartAndReportActive(bool in_isr)
333{
334 if (!StartActiveTransfer(in_isr))
335 {
336 write_port_->Finish(in_isr, ErrorCode::FAILED, tx_double_buffer_.ActiveInfo());
337 ClearActiveTx();
338 return false;
339 }
340
341 // Keep aligned with STM/CH: once next op is kicked to HW, report it finished.
342 write_port_->Finish(in_isr, ErrorCode::OK, tx_double_buffer_.ActiveInfo());
343 if (!tx_busy_.IsSet() && tx_double_buffer_.HasActive())
344 {
345 OnTxTransferDone(in_isr, ErrorCode::OK);
346 }
347 return true;
348}
349
350// 清空 TX FIFO 并关闭 TX 中断 / Flush TX FIFO and disable TX interrupt.
351void IRAM_ATTR ESP32CDCJtag::StopTxTransfer()
352{
353 usb_serial_jtag_ll_txfifo_flush();
354 usb_serial_jtag_ll_disable_intr_mask(TX_INTR_MASK);
355}
356
357// 先收尾 active,再处理 pending / Finish active first, then handle pending.
358void IRAM_ATTR ESP32CDCJtag::OnTxTransferDone(bool in_isr, ErrorCode result)
359{
360 Flag::ScopedRestore tx_flag(in_tx_isr_);
361 tx_busy_.Clear();
362
363 ClearActiveTx();
364
365 if ((result != ErrorCode::OK) && tx_double_buffer_.HasPending())
366 {
367 write_port_->Finish(in_isr, ErrorCode::FAILED, tx_double_buffer_.PendingInfo());
368 ClearPendingTx();
369 }
370
371 if (result != ErrorCode::OK)
372 {
373 StopTxTransfer();
374 return;
375 }
376
377 if (!tx_double_buffer_.HasPending())
378 {
379 StopTxTransfer();
380 return;
381 }
382
383 if (StartPendingTxIfIdle(in_isr))
384 {
385 if (!tx_double_buffer_.HasPending())
386 {
387 (void)LoadPendingTxFromQueue(in_isr);
388 }
389 return;
390 }
391
392 StopTxTransfer();
393}
394
395// TX 发起路径只做两件事:空闲时启动新的 active,或忙时补一个 pending。
396// TX initiation only does two things: start a new active when idle, or preload
397// one pending request while busy.
398ErrorCode IRAM_ATTR ESP32CDCJtag::TryStartTx(bool in_isr)
399{
400 if (in_tx_isr_.IsSet())
401 {
402 return ErrorCode::PENDING;
403 }
404
405 if (!tx_busy_.IsSet() && !tx_double_buffer_.HasActive() &&
406 !tx_double_buffer_.HasPending())
407 {
408 if (!LoadActiveTxFromQueue(in_isr))
409 {
410 return ErrorCode::PENDING;
411 }
412
413 if (!StartActiveTransfer(in_isr))
414 {
415 ClearActiveTx();
416 return ErrorCode::FAILED;
417 }
418
419 if (!tx_busy_.IsSet() && tx_double_buffer_.HasActive())
420 {
421 OnTxTransferDone(in_isr, ErrorCode::OK);
422 }
423
424 return ErrorCode::OK;
425 }
426
427 if (tx_busy_.IsSet() && !tx_double_buffer_.HasPending())
428 {
429 (void)LoadPendingTxFromQueue(in_isr);
430 }
431
432 return ErrorCode::PENDING;
433}
434
435// 分开处理 RX packet 和 TX empty / Dispatch RX packet and TX empty separately.
436void IRAM_ATTR ESP32CDCJtag::HandleInterrupt()
437{
438 const uint32_t status = usb_serial_jtag_ll_get_intsts_mask();
439
440 const uint32_t rx_status = status & RX_INTR_MASK;
441 if (rx_status != 0U)
442 {
443 usb_serial_jtag_ll_clr_intsts_mask(rx_status);
444 DrainRxToQueue(true);
445 }
446
447 const uint32_t tx_status = status & TX_INTR_MASK;
448 if (tx_status == 0U)
449 {
450 return;
451 }
452
453 usb_serial_jtag_ll_clr_intsts_mask(tx_status);
454
455 Flag::ScopedRestore tx_flag(in_tx_isr_);
456 const bool was_busy = tx_busy_.IsSet();
457 (void)PumpTx(true);
458 if (was_busy && !tx_busy_.IsSet())
459 {
460 OnTxTransferDone(true, ErrorCode::OK);
461 }
462}
463
464} // namespace LibXR
465
466#endif
LibXR 命名空间
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举
ErrorCode(* ReadFun)(ReadPort &port, bool in_isr)
Function pointer type for read notifications.
ErrorCode(* WriteFun)(WritePort &port, bool in_isr)
Function pointer type for write operations.
OwnerType * ContainerOf(MemberType *ptr, MemberType OwnerType::*member) noexcept
通过成员指针恢复其所属对象指针