libxr  1.0
Want to be the best embedded framework
Loading...
Searching...
No Matches
uart_dma_tx_model.hpp
1#pragma once
2
3#include <cstddef>
4#include <cstdint>
5
6#include "double_buffer.hpp"
7#include "libxr_assert.hpp"
8#include "libxr_rw.hpp"
9#include "serialized_service.hpp"
10
11namespace LibXR
12{
13
27template <typename Backend>
29{
30 public:
35 UartDmaTxModel(Backend& backend, WritePort& port, RawData storage)
36 : backend_(backend), port_(port), buffers_(storage)
37 {
38 }
39
47 ErrorCode Submit(bool in_isr)
48 {
49 SubmitContext context{};
50 (void)service_.Invoke(EventMask(TxEvent::WRITE),
51 [this, in_isr, &context](uint32_t events) noexcept
52 { ServiceTx(events, in_isr, &context); });
53 return context.result;
54 }
55
60 void OnTransferDone(bool in_isr)
61 {
62 (void)service_.Invoke(EventMask(TxEvent::COMPLETE),
63 [this, in_isr](uint32_t events) noexcept
64 { ServiceTx(events, in_isr, nullptr); });
65 }
66
74 void OnTransferError(bool in_isr)
75 {
76 (void)service_.Invoke(EventMask(TxEvent::ERROR),
77 [this, in_isr](uint32_t events) noexcept
78 { ServiceTx(events, in_isr, nullptr); });
79 }
80
91 void RequestConfig(bool in_isr)
92 {
93 backend_.OnConfigRequested();
94 (void)service_.Invoke(EventMask(TxEvent::CONFIG),
95 [this, in_isr](uint32_t events) noexcept
96 { ServiceTx(events, in_isr, nullptr); });
97 }
98
104 [[nodiscard]] bool IsBusy() const { return busy_; }
105
106 [[nodiscard]] uint8_t* Buffer(int block) const { return buffers_.Buffer(block); }
107
108 [[nodiscard]] size_t BufferSize() const { return buffers_.Size(); }
109
110 private:
111 enum class TxEvent : uint32_t
112 {
113 WRITE = 1U << 0U,
114 COMPLETE = 1U << 1U,
115 ERROR = 1U << 2U,
116 CONFIG = 1U << 3U,
117 };
118
120 {
122 };
123
124 static constexpr uint32_t EventMask(TxEvent event)
125 {
126 return static_cast<uint32_t>(event);
127 }
128
129 static constexpr bool HasEvent(uint32_t events, TxEvent event)
130 {
131 return (events & EventMask(event)) != 0U;
132 }
133
134 void ServiceTx(uint32_t events, bool in_isr, SubmitContext* submit) noexcept
135 {
136 if (HasEvent(events, TxEvent::CONFIG))
137 {
138 const bool resume_tx = ApplyConfig(in_isr);
139 if (!config_boundary_valid_ && resume_tx)
140 {
141 (void)service_.Invoke(EventMask(TxEvent::WRITE),
142 [this, in_isr](uint32_t pending_events) noexcept
143 { ServiceTx(pending_events, in_isr, nullptr); });
144 }
145 return;
146 }
147
148 // A CONFIG request may be waiting for RX hardware ownership. Keep later WRITE and
149 // terminal notifications coalesced in their level-triggered facts, but do not let
150 // them move the fixed old-prefix boundary before CONFIG is retried.
151 if (config_boundary_valid_)
152 {
153 return;
154 }
155
156 bool terminal = false;
157 if (HasEvent(events, TxEvent::ERROR))
158 {
159 terminal = ReleaseActive();
160 }
161 else if (HasEvent(events, TxEvent::COMPLETE))
162 {
163 terminal = ReleaseActive();
164 }
165
166 if (terminal && PromoteAndStartPending(in_isr))
167 {
168 return;
169 }
170
171 if (!busy_ && (active_length_ == 0U))
172 {
173 StartQueuedActive(in_isr, submit);
174 }
175
176 if (busy_)
177 {
178 (void)StageNextPending(in_isr);
179 }
180 }
181
182 bool PopPayload(uint8_t* destination, size_t size)
183 {
184 const ErrorCode result = port_.queue_data_->PopBatch(destination, size);
185 ASSERT(result == ErrorCode::OK);
186 return result == ErrorCode::OK;
187 }
188
189 bool DiscardPayload(size_t size)
190 {
191 while (size > 0U)
192 {
193 const size_t chunk = size > buffers_.Size() ? buffers_.Size() : size;
194 if (!PopPayload(buffers_.ActiveBuffer(), chunk))
195 {
196 return false;
197 }
198 size -= chunk;
199 }
200 return true;
201 }
202
203 bool PopActiveInfo(WriteInfoBlock& info)
204 {
205 const ErrorCode result = port_.queue_info_->Pop(info);
206 ASSERT(result == ErrorCode::OK);
207 return result == ErrorCode::OK;
208 }
209
210 bool StagePending(const WriteInfoBlock& info)
211 {
212 if (pending_valid_ || !PopPayload(buffers_.PendingBuffer(), info.data.size_))
213 {
214 return false;
215 }
216
217 pending_valid_ = true;
218 return true;
219 }
220
221 bool StageNextPending(bool in_isr)
222 {
223 while (!pending_valid_)
224 {
225 WriteInfoBlock info{};
226 if (port_.queue_info_->Peek(info) != ErrorCode::OK)
227 {
228 return false;
229 }
230
231 if (info.data.size_ > buffers_.Size())
232 {
233 if (!DiscardPayload(info.data.size_) || !PopActiveInfo(info))
234 {
235 ASSERT(false);
236 return false;
237 }
238 port_.Finish(in_isr, ErrorCode::FAILED, info);
239 continue;
240 }
241
242 return StagePending(info);
243 }
244 return false;
245 }
246
247 bool PromotePending(WriteInfoBlock& info)
248 {
249 if (!pending_valid_)
250 {
251 return false;
252 }
253
254 pending_valid_ = false;
255 buffers_.FlipActiveBlock();
256 if (!PopActiveInfo(info))
257 {
258 return false;
259 }
260
261 active_length_ = info.data.size_;
262 return true;
263 }
264
265 bool StartActive()
266 {
267 ASSERT(active_length_ > 0U);
268 busy_ = true;
269 if (backend_.StartDmaTx(buffers_.ActiveBuffer(), active_length_,
270 buffers_.ActiveBlock()))
271 {
272 return true;
273 }
274
275 busy_ = false;
276 return false;
277 }
278
279 bool ReleaseActive()
280 {
281 if (!busy_)
282 {
283 return false;
284 }
285
286 busy_ = false;
287 ClearActive();
288 return true;
289 }
290
291 bool PromoteAndStartPending(bool in_isr)
292 {
293 WriteInfoBlock info{};
294 if (!PromotePending(info))
295 {
296 return false;
297 }
298
299 const bool started = StartActive();
300 if (!started)
301 {
302 ClearActive();
303 }
304 port_.Finish(in_isr, started ? ErrorCode::OK : ErrorCode::FAILED, info);
305
306 if (!started)
307 {
308 RequestConfig(in_isr);
309 return true;
310 }
311
312 (void)StageNextPending(in_isr);
313 return true;
314 }
315
316 void StartQueuedActive(bool in_isr, SubmitContext* submit)
317 {
318 WriteInfoBlock info{};
319 if (port_.queue_info_->Peek(info) != ErrorCode::OK)
320 {
321 return;
322 }
323
324 if (info.data.size_ > buffers_.Size())
325 {
326 if (!DiscardPayload(info.data.size_) || !PopActiveInfo(info))
327 {
328 ASSERT(false);
329 return;
330 }
331 if (submit != nullptr)
332 {
333 submit->result = ErrorCode::FAILED;
334 }
335 else
336 {
337 port_.Finish(in_isr, ErrorCode::FAILED, info);
338 }
339 return;
340 }
341
342 if (!PopPayload(buffers_.ActiveBuffer(), info.data.size_) || !PopActiveInfo(info))
343 {
344 ASSERT(false);
345 if (submit != nullptr)
346 {
347 submit->result = ErrorCode::FAILED;
348 }
349 return;
350 }
351
352 active_length_ = info.data.size_;
353 const bool started = StartActive();
354 if (!started)
355 {
356 ClearActive();
357 }
358
359 if (submit != nullptr)
360 {
361 submit->result = started ? ErrorCode::OK : ErrorCode::FAILED;
362 }
363 else
364 {
365 port_.Finish(in_isr, started ? ErrorCode::OK : ErrorCode::FAILED, info);
366 if (!started)
367 {
368 RequestConfig(in_isr);
369 }
370 }
371 }
372
373 bool ApplyConfig(bool in_isr)
374 {
375 // Metadata is the operation publication boundary. Fix the old prefix before any
376 // Finish callback can append another request. Payload without metadata belongs to
377 // an in-progress producer and is intentionally left for its later WRITE event.
378 // metadata 是操作发布边界。在任何 Finish callback 可能追加新请求前固定旧前缀;
379 // 只有 payload、尚无 metadata 的生产者仍在发布中,留给其后续 WRITE 事件处理。
380 if (!config_boundary_valid_)
381 {
382 config_prefix_count_ = port_.queue_info_->Size();
383 config_boundary_valid_ = true;
384 }
385
386 if (!backend_.ApplyPendingConfig(in_isr))
387 {
388 return false;
389 }
390
391 size_t remaining = config_prefix_count_;
392 config_prefix_count_ = 0U;
393 config_boundary_valid_ = false;
394
395 (void)ReleaseActive();
396
397 if (pending_valid_)
398 {
399 ASSERT(remaining > 0U);
400 pending_valid_ = false;
401 WriteInfoBlock info{};
402 if (!PopActiveInfo(info))
403 {
404 ASSERT(false);
405 return false;
406 }
407 --remaining;
408 port_.Finish(in_isr, ErrorCode::FAILED, info);
409 }
410
411 FailPublishedQueued(in_isr, remaining);
412 return backend_.OnConfigApplied(in_isr);
413 }
414
415 void FailPublishedQueued(bool in_isr, size_t count)
416 {
417 for (size_t index = 0U; index < count; ++index)
418 {
419 WriteInfoBlock info{};
420 if (port_.queue_info_->Peek(info) != ErrorCode::OK)
421 {
422 ASSERT(false);
423 return;
424 }
425 if (!DiscardPayload(info.data.size_) || !PopActiveInfo(info))
426 {
427 ASSERT(false);
428 return;
429 }
430 port_.Finish(in_isr, ErrorCode::FAILED, info);
431 }
432 }
433
434 void ClearActive() { active_length_ = 0U; }
435
436 Backend& backend_;
437 WritePort& port_;
438 DoubleBuffer buffers_;
439 SerializedService service_{};
440 size_t active_length_ = 0U;
441 size_t config_prefix_count_ = 0U;
442 bool busy_ = false;
443 bool pending_valid_ = false;
444 bool config_boundary_valid_ = false;
445};
446
447} // namespace LibXR
int ActiveBlock() const
获取当前活动缓冲区编号 Returns the current active block index
void FlipActiveBlock()
翻转当前活动缓冲区编号 Flips the current active block index
size_t Size() const
获取每个缓冲区的大小(单位:字节) Gets the size of each buffer in bytes
uint8_t * Buffer(int block) const
获取指定编号缓冲区的指针 Returns the pointer of the specified block
uint8_t * ActiveBuffer() const
获取当前正在使用的缓冲区指针 Returns the currently active buffer
uint8_t * PendingBuffer() const
获取备用缓冲区的指针 Returns the pending (inactive) buffer
可写原始数据视图 / Mutable raw data view
ErrorCode PopBatch(Data *data, size_t size)
批量弹出多个 payload。
bool Invoke(uint32_t events, Handler &&handler) noexcept
发布事件并尝试执行服务 / Publish events and try to run the service
UART 单次 DMA 发送执行模型 / UART one-shot DMA TX execution model.
UartDmaTxModel(Backend &backend, WritePort &port, RawData storage)
绑定平台后端、写端口和 DMA 双缓冲区 / Bind the backend, write port, and DMA double buffer
ErrorCode Submit(bool in_isr)
发布 WRITE 事件并推进队首请求 / Publish WRITE and advance the head request
void OnTransferDone(bool in_isr)
发布正常完成事件 / Publish a normal completion event
bool IsBusy() const
查询 DMA 是否持有 active 请求 / Check whether DMA owns an active request
void RequestConfig(bool in_isr)
发布最高优先级配置事件 / Publish the highest-priority configuration event
void OnTransferError(bool in_isr)
在平台恢复旧 DMA 后发布错误终止事件 / Publish an error terminal event after platform recovery
WritePort class for handling write operations.
void Finish(bool in_isr, ErrorCode ans, WriteInfoBlock &info)
更新写入操作的状态。 Updates the status of the write operation.
SPSCQueue< WriteInfoBlock > * queue_info_
Metadata queue for pending write batches. 挂起写批次的元数据队列。
SPSCQueue< uint8_t > * queue_data_
Payload queue for pending write bytes. 挂起写入字节的数据队列。
LibXR 命名空间
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举
@ FAILED
操作失败 | Operation failed
@ PENDING
等待中 | Pending
@ OK
操作成功 | Operation successful