1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://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, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_uart_mcuxpresso/dma_uart_nonblocking.h"
16
17 #include <optional>
18
19 #include "fsl_common_arm.h"
20 #include "fsl_dma.h"
21 #include "fsl_flexcomm.h"
22 #include "fsl_usart_dma.h"
23 #include "pw_assert/check.h"
24 #include "pw_bytes/byte_builder.h"
25 #include "pw_log/log.h"
26 #include "pw_preprocessor/util.h"
27
28 namespace pw::uart {
29
30 // Deinitialize the DMA channels and USART.
Deinit()31 void DmaUartMcuxpressoNonBlocking::Deinit() {
32 if (!initialized_) {
33 return;
34 }
35
36 config_.tx_dma_ch.Disable();
37 config_.rx_dma_ch.Disable();
38
39 USART_Deinit(config_.usart_base);
40 clock_tree_element_controller_.Release().IgnoreError();
41 initialized_ = false;
42 }
43
~DmaUartMcuxpressoNonBlocking()44 DmaUartMcuxpressoNonBlocking::~DmaUartMcuxpressoNonBlocking() { Deinit(); }
45
46 // Initialize the USART and DMA channels based on the configuration
47 // specified during object creation.
Init()48 Status DmaUartMcuxpressoNonBlocking::Init() {
49 if (config_.usart_base == nullptr) {
50 return Status::InvalidArgument();
51 }
52 if (config_.baud_rate == 0) {
53 return Status::InvalidArgument();
54 }
55
56 usart_config_t defconfig;
57 USART_GetDefaultConfig(&defconfig);
58
59 defconfig.baudRate_Bps = config_.baud_rate;
60 defconfig.enableHardwareFlowControl = config_.flow_control;
61 defconfig.parityMode = config_.parity;
62 defconfig.enableTx = true;
63 defconfig.enableRx = true;
64
65 PW_TRY(clock_tree_element_controller_.Acquire());
66 flexcomm_clock_freq_ =
67 CLOCK_GetFlexcommClkFreq(FLEXCOMM_GetInstance(config_.usart_base));
68 status_t status =
69 USART_Init(config_.usart_base, &defconfig, flexcomm_clock_freq_);
70 if (status != kStatus_Success) {
71 clock_tree_element_controller_.Release().IgnoreError();
72 return Status::Internal();
73 }
74
75 tx_data_.tx_idx = 0;
76
77 rx_data_.data_received = 0;
78 rx_data_.data_copied = 0;
79 rx_data_.ring_buffer_read_idx = 0;
80 rx_data_.ring_buffer_write_idx = 0;
81
82 {
83 // We need exclusive access to INPUTMUX registers, as it is used by many DMA
84 // peripherals.
85 std::lock_guard lock(interrupt_lock_);
86
87 // Temporarily enable clock to inputmux, so that RX and TX DMA requests can
88 // get enabled.
89 INPUTMUX_Init(INPUTMUX);
90 INPUTMUX_EnableSignal(
91 INPUTMUX, config_.rx_input_mux_dmac_ch_request_en, true);
92 INPUTMUX_EnableSignal(
93 INPUTMUX, config_.tx_input_mux_dmac_ch_request_en, true);
94 INPUTMUX_Deinit(INPUTMUX);
95 }
96
97 config_.tx_dma_ch.Enable();
98 config_.rx_dma_ch.Enable();
99
100 // Initialized enough for Deinit code to handle any errors from here.
101 initialized_ = true;
102
103 status = USART_TransferCreateHandleDMA(config_.usart_base,
104 &uart_dma_handle_,
105 DmaCallback,
106 this,
107 config_.tx_dma_ch.handle(),
108 config_.rx_dma_ch.handle());
109
110 if (status != kStatus_Success) {
111 Deinit();
112 return Status::Internal();
113 }
114
115 {
116 std::lock_guard lock(interrupt_lock_);
117
118 rx_data_.request.valid = false;
119 tx_data_.request.valid = false;
120 tx_data_.flush_request.valid = false;
121
122 // Begin reading into the ring buffer.
123 TriggerReadDmaIntoRingBuffer();
124 }
125
126 return OkStatus();
127 }
128
DoEnable(bool enable)129 Status DmaUartMcuxpressoNonBlocking::DoEnable(bool enable) {
130 if (enable == initialized_) {
131 return OkStatus();
132 }
133
134 if (enable) {
135 return Init();
136 } else {
137 Deinit();
138 return OkStatus();
139 }
140 }
141
142 // Trigger a RX DMA into the ring buffer.
143 // The ring buffer is the DMA target when there is NOT an active read request.
TriggerReadDmaIntoRingBuffer()144 void DmaUartMcuxpressoNonBlocking::TriggerReadDmaIntoRingBuffer() {
145 PW_DCHECK(!rx_data_.request.valid);
146
147 rx_data_.target = DmaRxTarget::kRingBuffer;
148
149 uint8_t* ring_buffer =
150 reinterpret_cast<uint8_t*>(rx_data_.ring_buffer.data());
151 rx_data_.transfer.data = &ring_buffer[rx_data_.ring_buffer_write_idx];
152
153 // Are we about to run off the end of the ring buffer?
154 if (rx_data_.ring_buffer_write_idx + kUsartDmaMaxTransferCount >
155 rx_data_.ring_buffer.size_bytes()) {
156 // Clamp the size of the transfer.
157 rx_data_.transfer.dataSize =
158 rx_data_.ring_buffer.size_bytes() - rx_data_.ring_buffer_write_idx;
159 } else {
160 // Otherwise, read the maximum number of bytes.
161 rx_data_.transfer.dataSize = kUsartDmaMaxTransferCount;
162 }
163
164 PW_DCHECK_UINT_LE(rx_data_.ring_buffer_write_idx + rx_data_.transfer.dataSize,
165 rx_data_.ring_buffer.size_bytes());
166
167 PW_LOG_DEBUG("TriggerReadDma(Ring) write_idx[%u-%u) size(%u)",
168 rx_data_.ring_buffer_write_idx,
169 rx_data_.ring_buffer_write_idx + rx_data_.transfer.dataSize,
170 rx_data_.transfer.dataSize);
171
172 USART_TransferReceiveDMA(
173 config_.usart_base, &uart_dma_handle_, &rx_data_.transfer);
174 }
175
176 // Trigger a RX DMA into the user buffer.
177 // The user buffer is the DMA target when there is an active read request.
TriggerReadDmaIntoUserBuffer()178 void DmaUartMcuxpressoNonBlocking::TriggerReadDmaIntoUserBuffer() {
179 PW_DCHECK(rx_data_.request.valid);
180
181 rx_data_.target = DmaRxTarget::kUserBuffer;
182
183 uint8_t* user_buffer =
184 reinterpret_cast<uint8_t*>(rx_data_.request.buffer.data());
185 rx_data_.transfer.data = &user_buffer[rx_data_.request.write_idx];
186
187 rx_data_.transfer.dataSize =
188 std::min(rx_data_.request.bytes_remaining, kUsartDmaMaxTransferCount);
189
190 PW_LOG_DEBUG("TriggerReadDma(User) write_idx[%u-%u) size(%u)",
191 rx_data_.request.write_idx,
192 rx_data_.request.write_idx + rx_data_.transfer.dataSize,
193 rx_data_.transfer.dataSize);
194
195 USART_TransferReceiveDMA(
196 config_.usart_base, &uart_dma_handle_, &rx_data_.transfer);
197 }
198
199 // Trigger a TX DMA from the user's buffer.
TriggerWriteDma()200 void DmaUartMcuxpressoNonBlocking::TriggerWriteDma() {
201 const uint8_t* tx_buffer =
202 reinterpret_cast<const uint8_t*>(tx_data_.buffer.data());
203 tx_data_.transfer.txData = &tx_buffer[tx_data_.tx_idx];
204
205 // If this is the final DMA transaction, we need to clamp the number of
206 // transfer bytes.
207 size_t bytes_remaining = tx_data_.buffer.size() - tx_data_.tx_idx;
208 tx_data_.transfer.dataSize =
209 std::min(bytes_remaining, kUsartDmaMaxTransferCount);
210
211 USART_TransferSendDMA(
212 config_.usart_base, &uart_dma_handle_, &tx_data_.transfer);
213 }
214
215 // Clear the RX DMA idle interrupt flag and returns whether the flag was set.
216 // This function is based on fsl_dma.cc::DMA_IRQHandle().
ClearRxDmaInterrupt()217 bool DmaUartMcuxpressoNonBlocking::ClearRxDmaInterrupt() {
218 const dma_handle_t* handle = uart_dma_handle_.rxDmaHandle;
219 const uint8_t channel_index = DMA_CHANNEL_INDEX(base, handle->channel);
220 const bool interrupt_enabled =
221 ((DMA_COMMON_REG_GET(handle->base, handle->channel, INTENSET) &
222 (1UL << channel_index)) != 0UL);
223 const bool channel_a_flag =
224 ((DMA_COMMON_REG_GET(handle->base, handle->channel, INTA) &
225 (1UL << channel_index)) != 0UL);
226 const bool channel_b_flag =
227 ((DMA_COMMON_REG_GET(handle->base, handle->channel, INTB) &
228 (1UL << channel_index)) != 0UL);
229
230 if (interrupt_enabled) {
231 if (channel_a_flag) {
232 DMA_COMMON_REG_SET(
233 handle->base, handle->channel, INTA, (1UL << channel_index));
234 }
235
236 if (channel_b_flag) {
237 DMA_COMMON_REG_SET(
238 handle->base, handle->channel, INTB, (1UL << channel_index));
239 }
240 }
241
242 const bool int_was_set =
243 interrupt_enabled && (channel_a_flag || channel_b_flag);
244 return int_was_set;
245 }
246
DoRead(ByteSpan rx_buffer,size_t min_bytes,Function<void (Status status,ConstByteSpan buffer)> && callback)247 Status DmaUartMcuxpressoNonBlocking::DoRead(
248 ByteSpan rx_buffer,
249 size_t min_bytes,
250 Function<void(Status status, ConstByteSpan buffer)>&& callback) {
251 size_t max_bytes = rx_buffer.size();
252 if (min_bytes == 0 || max_bytes == 0 || min_bytes > max_bytes) {
253 return Status::InvalidArgument();
254 }
255
256 // We must grab the interrupt lock before reading the `valid` flag to avoid
257 // racing with `TxRxCompletionCallback()`.
258 std::lock_guard lock(interrupt_lock_);
259
260 if (rx_data_.request.valid) {
261 return Status::Unavailable();
262 }
263 rx_data_.request.valid = true;
264
265 // The user has requested at least `min_bytes`, but will take up to
266 // `max_bytes`. Our strategy is to copy as much buffered data as we can right
267 // now, up to `max_bytes`. We start by consuming bytes from the ring buffer.
268 // After exhausting the ring buffer, we cancel the in-flight DMA early to get
269 // any data the DMA transferred, but hasn't been accounted for.
270 // Since the DMA transfer size is large, data sitting in the transfer buffer
271 // could potentially be quite old. If we _still_ don't have enough, we setup
272 // a DMA for the remaining amount, directly into the user's buffer.
273 //
274 // We can split this up into three scenarios:
275 // 1. The ring buffer has enough data to immediately complete the request.
276 // 2. We can complete the request only if we cancel the in-flight DMA.
277 // 3. We don't have enough data. Consume any bytes we have and start a DMA
278 // directly into the user's buffer.
279 //
280 // (Note, scenarios 1 and 2 both complete the read request within the
281 // `DoRead()` call.)
282 //
283 // The code below handles these three scenarios, but slightly reorders things
284 // for the sake of optimization.
285
286 // First, if we know the ring buffer isn't going to be enough, get the
287 // DMA abort out of the way for scenarios 2 and 3.
288 bool dma_aborted = false;
289 size_t bytes_in_ring_buffer = rx_data_.data_received - rx_data_.data_copied;
290
291 PW_LOG_DEBUG("DoRead min_bytes(%u) ring_bytes(%u) read_idx(%u) write_idx(%u)",
292 min_bytes,
293 bytes_in_ring_buffer,
294 rx_data_.ring_buffer_read_idx,
295 rx_data_.ring_buffer_write_idx);
296
297 if (bytes_in_ring_buffer < min_bytes) {
298 // Cancel the DMA
299 USART_TransferAbortReceiveDMA(config_.usart_base, &uart_dma_handle_);
300
301 // Get the number of bytes that the transfer didn't fulfill.
302 size_t bytes_remaining =
303 DMA_GetRemainingBytes(uart_dma_handle_.rxDmaHandle->base,
304 uart_dma_handle_.rxDmaHandle->channel);
305 size_t bytes_received = rx_data_.transfer.dataSize - bytes_remaining;
306
307 if (bytes_remaining == 0) {
308 // We raced a completed RX DMA.
309 // If we exit the critical section without doing anything, the DMA idle
310 // interrupt will fire and call `TxRxCompletionCallback()`.
311 // Clear the interrupt flag to prevent the ISR from firing.
312 // We'll manually handle the completion here instead.
313 const bool int_was_set = ClearRxDmaInterrupt();
314 PW_DCHECK(int_was_set);
315 HandleCompletedRxIntoRingBuffer();
316 } else {
317 // Otherwise, the DMA was successfully cancelled, with partial data
318 // written to the ring buffer. Manually fix up any ring buffer accounting.
319 rx_data_.ring_buffer_write_idx += bytes_received;
320 rx_data_.data_received += bytes_received;
321
322 if (rx_data_.ring_buffer_write_idx >= rx_data_.ring_buffer.size_bytes()) {
323 PW_LOG_DEBUG("ring_buffer_write_idx rolled over in DoRead");
324 rx_data_.ring_buffer_write_idx = 0;
325 }
326 }
327
328 bytes_in_ring_buffer += bytes_received;
329
330 // Data from the cancelled transfer should now be accounted for.
331
332 dma_aborted = true;
333 }
334
335 // Now that we've dealt with any accounting issues from DMA cancellation,
336 // we know if the ring buffer contains enough data to complete the request
337 // immediately.
338 const size_t copy_size = std::min(max_bytes, bytes_in_ring_buffer);
339 const bool request_completed_internally = copy_size >= min_bytes;
340
341 // Before we start copying out of the ring buffer, let's start the next DMA.
342 // We want to minimize the time spent without a DMA in-flight, as that risks
343 // data loss.
344 if (request_completed_internally) {
345 // We're about to complete the request with data just from the ring buffer.
346 rx_data_.request.valid = false;
347 if (dma_aborted) {
348 // If we cancelled the DMA to complete this request, kick off the next
349 // one manually.
350 TriggerReadDmaIntoRingBuffer();
351 }
352 } else {
353 // We still need more data to complete the request.
354 // Configure the next DMA to point directly into the user buffer.
355 // Note we can only request enough for `min_bytes`. If the user calls
356 // Read() in a tight loop with `min_bytes = 1`, this can result in many
357 // single-byte DMA transactions.
358 rx_data_.request.buffer = rx_buffer;
359 rx_data_.request.write_idx = copy_size;
360 rx_data_.request.bytes_remaining = min_bytes - copy_size;
361 rx_data_.request.bytes_requested = min_bytes;
362 rx_data_.request.callback = std::move(callback);
363 rx_data_.request.valid = true;
364 TriggerReadDmaIntoUserBuffer();
365 }
366
367 // Copy all the data we can from the ring buffer.
368 // This is needed in all three scenarios.
369 if (copy_size > 0) {
370 ByteSpan ring_buffer = rx_data_.ring_buffer;
371 auto bb = ByteBuilder{rx_buffer};
372 PW_LOG_DEBUG(
373 "copy (%u bytes) @ [%u]", copy_size, rx_data_.ring_buffer_read_idx);
374
375 // If the data crosses the end of the ring_buffer, we need to do a split
376 // copy operation.
377 if (rx_data_.ring_buffer_read_idx + copy_size > ring_buffer.size_bytes()) {
378 // The first copy is any bytes at the end of the buffer.
379 size_t first_copy_size =
380 ring_buffer.size_bytes() - rx_data_.ring_buffer_read_idx;
381 bb.append(
382 ring_buffer.subspan(rx_data_.ring_buffer_read_idx, first_copy_size));
383
384 // The second copy starts back at offset 0.
385 size_t second_copy_size = copy_size - first_copy_size;
386 bb.append(ring_buffer.subspan(0, second_copy_size));
387 rx_data_.ring_buffer_read_idx = second_copy_size;
388
389 PW_LOG_DEBUG("split copy first(%u bytes) second(%u bytes)",
390 first_copy_size,
391 second_copy_size);
392 } else {
393 // Otherwise, it's just a normal copy.
394 PW_DCHECK_UINT_LE(rx_data_.ring_buffer_read_idx + copy_size,
395 ring_buffer.size_bytes());
396 bb.append(ring_buffer.subspan(rx_data_.ring_buffer_read_idx, copy_size));
397 rx_data_.ring_buffer_read_idx += copy_size;
398
399 // But we still need to do the ring buffer accounting for our reader
400 // index!
401 if (rx_data_.ring_buffer_read_idx == ring_buffer.size_bytes()) {
402 PW_LOG_DEBUG("read_idx rollover to 0 in DoRead");
403 rx_data_.ring_buffer_read_idx = 0;
404 }
405 }
406
407 // TODO(shined): `ring_buffer_read_idx` could be deleted entirely in a
408 // refactor, since `data_copied` encodes the same information (if the
409 // ring_buffer size is aligned.)
410 rx_data_.data_copied += copy_size;
411 }
412
413 // Now that we've copied data out of the ring buffer, we can call the user
414 // callback if the request has been completed.
415 if (request_completed_internally) {
416 PW_LOG_DEBUG("request completed in DoRead");
417 callback(OkStatus(), rx_data_.request.buffer.first(copy_size));
418 }
419
420 return OkStatus();
421 }
422
DoWrite(ConstByteSpan tx_buffer,Function<void (StatusWithSize status)> && callback)423 Status DmaUartMcuxpressoNonBlocking::DoWrite(
424 ConstByteSpan tx_buffer, Function<void(StatusWithSize status)>&& callback) {
425 if (tx_buffer.size() == 0) {
426 return Status::InvalidArgument();
427 }
428
429 PW_LOG_DEBUG("DoWrite: size(%u)", tx_buffer.size());
430
431 std::lock_guard lock(interrupt_lock_);
432
433 if (tx_data_.request.valid) {
434 return Status::Unavailable();
435 }
436 tx_data_.request.valid = true;
437
438 tx_data_.buffer = tx_buffer;
439 tx_data_.tx_idx = 0;
440 tx_data_.request.callback = std::move(callback);
441
442 // Start the DMA. If multiple DMA transactions are needed, the completion
443 // callback will set up subsequent transactions.
444 TriggerWriteDma();
445
446 return OkStatus();
447 }
448
449 // Static wrapper method called by the DMA completion ISR.
DmaCallback(USART_Type * base,usart_dma_handle_t * handle,status_t dma_status,void * userdata)450 void DmaUartMcuxpressoNonBlocking::DmaCallback(USART_Type* base,
451 usart_dma_handle_t* handle,
452 status_t dma_status,
453 void* userdata) {
454 auto* uart = static_cast<DmaUartMcuxpressoNonBlocking*>(userdata);
455 PW_CHECK_PTR_EQ(base, uart->config_.usart_base);
456 PW_CHECK_PTR_EQ(handle, &uart->uart_dma_handle_);
457
458 return uart->TxRxCompletionCallback(dma_status);
459 }
460
461 // This helper function is called by `TxRxCompletionCallback()` after a DMA
462 // transaction into the user buffer.
HandleCompletedRxIntoUserBuffer()463 void DmaUartMcuxpressoNonBlocking::HandleCompletedRxIntoUserBuffer() {
464 rx_data_.request.bytes_remaining -= rx_data_.transfer.dataSize;
465 rx_data_.request.write_idx += rx_data_.transfer.dataSize;
466
467 // Have we completed the read request?
468 if (rx_data_.request.bytes_remaining == 0) {
469 // Call the user's completion callback and invalidate the request.
470 PW_LOG_DEBUG("request completed in callback");
471 rx_data_.request.callback(
472 OkStatus(),
473 rx_data_.request.buffer.first(rx_data_.request.bytes_requested));
474 rx_data_.request.valid = false;
475 }
476 }
477
478 // This helper function is called by `TxRxCompletionCallback()` after a DMA
479 // transaction into the ring buffer. Additionally, it is called by `DoRead()`
480 // and `DoCancelRead()` after cancelling a DMA transaction _only_ if the
481 // cancellation raced with DMA completion.
HandleCompletedRxIntoRingBuffer()482 void DmaUartMcuxpressoNonBlocking::HandleCompletedRxIntoRingBuffer() {
483 rx_data_.ring_buffer_write_idx += rx_data_.transfer.dataSize;
484 rx_data_.data_received += rx_data_.transfer.dataSize;
485
486 PW_DCHECK_UINT_LE(rx_data_.data_received - rx_data_.data_copied,
487 rx_data_.ring_buffer.size_bytes());
488 PW_DCHECK_UINT_LE(rx_data_.ring_buffer_write_idx,
489 rx_data_.ring_buffer.size_bytes());
490 if (rx_data_.ring_buffer_write_idx == rx_data_.ring_buffer.size_bytes()) {
491 PW_LOG_DEBUG("ring_buffer_write_idx rolled over in callback");
492 rx_data_.ring_buffer_write_idx = 0;
493 }
494 }
495
496 // This callback is called by both the RX and TX interrupt handlers.
497 // It is called upon completion of a DMA transaction.
TxRxCompletionCallback(status_t status)498 void DmaUartMcuxpressoNonBlocking::TxRxCompletionCallback(status_t status) {
499 std::lock_guard lock(interrupt_lock_);
500
501 if (status == kStatus_USART_RxIdle) {
502 // RX transaction complete
503
504 // Was this DMA targeting the user buffer or the ring buffer?
505 if (rx_data_.target == DmaRxTarget::kUserBuffer) {
506 // We're DMA-ing directly into the user's buffer.
507 HandleCompletedRxIntoUserBuffer();
508 } else {
509 // We're DMA-ing into the ring buffer.
510 HandleCompletedRxIntoRingBuffer();
511 }
512
513 // Trigger the next DMA into either the user buffer or ring buffer,
514 // depending on whether we are servicing a user request or not.
515 if (rx_data_.request.valid) {
516 TriggerReadDmaIntoUserBuffer();
517 } else {
518 TriggerReadDmaIntoRingBuffer();
519 }
520 } else if (status == kStatus_USART_TxIdle && tx_data_.request.valid) {
521 // TX transaction complete
522 // This codepath runs only when there is a valid TX request, as writes only
523 // come from the user.
524 tx_data_.tx_idx += tx_data_.transfer.dataSize;
525 // Is the request complete?
526 if (tx_data_.tx_idx == tx_data_.buffer.size_bytes()) {
527 tx_data_.request.callback(StatusWithSize(tx_data_.buffer.size_bytes()));
528 tx_data_.request.valid = false;
529 CompleteFlushRequest(OkStatus());
530 } else {
531 // No, set up a followup DMA.
532 PW_CHECK_INT_LT(tx_data_.tx_idx, tx_data_.buffer.size_bytes());
533 TriggerWriteDma();
534 }
535 }
536 }
537
DoCancelRead()538 bool DmaUartMcuxpressoNonBlocking::DoCancelRead() {
539 std::lock_guard lock(interrupt_lock_);
540
541 if (!rx_data_.request.valid) {
542 return false;
543 }
544
545 // There _must_ be an RX DMA directly targeting the user buffer.
546 // We know this because we are in a critical section and the request is valid.
547
548 // Cancel the in-flight DMA.
549 USART_TransferAbortReceiveDMA(config_.usart_base, &uart_dma_handle_);
550
551 // Get the number of bytes the DMA transaction was short by.
552 size_t dma_bytes_remaining =
553 DMA_GetRemainingBytes(uart_dma_handle_.rxDmaHandle->base,
554 uart_dma_handle_.rxDmaHandle->channel);
555
556 if (dma_bytes_remaining == 0) {
557 // We raced a completed RX DMA.
558 // If we exit the critical section without doing anything, the DMA idle
559 // interrupt will fire and call `TxRxCompletionCallback()`.
560 if (rx_data_.request.bytes_remaining == 0) {
561 // ...and that DMA also completed the user's request.
562 // Fail the cancellation; `TxRxCompletionCallback()` will complete the
563 // transaction and call the user callback.
564 // This is the only scenario where the read request could already be
565 // complete.
566 // This race is why we must use `DMA_GetRemainingBytes()` instead of
567 // `USART_TransferGetReceiveCountDMA()`.
568 return false;
569 } else {
570 // ...but that DMA was not enough to complete the request.
571 // Clear the interrupt flag to prevent the ISR from firing.
572 // We'll manually handle the completion here instead.
573 const bool int_was_set = ClearRxDmaInterrupt();
574 PW_DCHECK(int_was_set);
575 HandleCompletedRxIntoRingBuffer();
576 }
577 } else {
578 // We successfully cancelled an in-flight DMA transaction.
579 // Account for the final bytes that got copied into the user buffer.
580 rx_data_.request.bytes_remaining -=
581 rx_data_.transfer.dataSize - dma_bytes_remaining;
582 }
583
584 size_t bytes_copied =
585 rx_data_.request.bytes_requested - rx_data_.request.bytes_remaining;
586 rx_data_.request.callback(Status::Cancelled(),
587 rx_data_.request.buffer.first(bytes_copied));
588 rx_data_.request.valid = false;
589
590 // Set up a new RX DMA into the ring buffer.
591 TriggerReadDmaIntoRingBuffer();
592
593 return true;
594 }
595
DoCancelWrite()596 bool DmaUartMcuxpressoNonBlocking::DoCancelWrite() {
597 std::lock_guard lock(interrupt_lock_);
598
599 if (!tx_data_.request.valid) {
600 return false;
601 }
602
603 // There is a TX DMA in-flight.
604 // We know this because we are in a critical section and the request is valid.
605
606 // Cancel the in-flight DMA.
607 USART_TransferAbortSendDMA(config_.usart_base, &uart_dma_handle_);
608
609 // Get the number of bytes the DMA transaction was short by.
610 size_t dma_bytes_remaining =
611 DMA_GetRemainingBytes(uart_dma_handle_.txDmaHandle->base,
612 uart_dma_handle_.txDmaHandle->channel);
613
614 if (dma_bytes_remaining == 0 &&
615 tx_data_.tx_idx + tx_data_.transfer.dataSize == tx_data_.buffer.size()) {
616 // We raced a completed TX DMA, and that DMA completed the user's request.
617 // The interrupt will fire once we exit this critical section.
618 // Fail the cancellation; the TxRxCompletionCallback will complete the
619 // transaction and call the user callback.
620 // This is the only scenario where the write request could already be
621 // complete.
622 // This race is why we must use `DMA_GetRemainingBytes()` instead of
623 // `USART_TransferGetSendCountDMA()`.
624 return false;
625 }
626
627 size_t bytes_transmitted =
628 tx_data_.tx_idx + (tx_data_.transfer.dataSize - dma_bytes_remaining);
629 tx_data_.request.callback(StatusWithSize::Cancelled(bytes_transmitted));
630 tx_data_.request.valid = false;
631
632 CompleteFlushRequest(Status::Aborted());
633
634 return true;
635 }
636
DoConservativeReadAvailable()637 size_t DmaUartMcuxpressoNonBlocking::DoConservativeReadAvailable() {
638 std::lock_guard lock(interrupt_lock_);
639
640 size_t bytes_received = rx_data_.data_received - rx_data_.data_copied;
641
642 uint32_t count = 0;
643 status_t status = USART_TransferGetReceiveCountDMA(
644 config_.usart_base, &uart_dma_handle_, &count);
645 if (status == kStatus_Success) {
646 bytes_received += count;
647 }
648
649 return count;
650 }
651
DoClearPendingReceiveBytes()652 Status DmaUartMcuxpressoNonBlocking::DoClearPendingReceiveBytes() {
653 std::lock_guard lock(interrupt_lock_);
654
655 if (rx_data_.request.valid) {
656 // It doesn't make sense to clear the receive buffer when a read request
657 // is in flight.
658 return Status::FailedPrecondition();
659 }
660
661 // Note: This only clears the ring buffer, not any bytes from the current
662 // DMA transaction. Those bytes could be quite old, and this function could
663 // be improved to also cancel the in-flight RX transfer.
664 size_t bytes_pending = rx_data_.data_received - rx_data_.data_copied;
665 rx_data_.ring_buffer_read_idx += bytes_pending;
666 rx_data_.ring_buffer_read_idx %= rx_data_.ring_buffer.size();
667 rx_data_.data_copied = rx_data_.data_received;
668
669 return OkStatus();
670 }
671
DoSetBaudRate(uint32_t baud_rate)672 Status DmaUartMcuxpressoNonBlocking::DoSetBaudRate(uint32_t baud_rate) {
673 if (baud_rate == 0) {
674 return Status::InvalidArgument();
675 }
676
677 config_.baud_rate = baud_rate;
678
679 if (!initialized_) {
680 return OkStatus();
681 }
682
683 status_t status = USART_SetBaudRate(
684 config_.usart_base, config_.baud_rate, flexcomm_clock_freq_);
685 switch (status) {
686 default:
687 return Status::Unknown();
688 case kStatus_USART_BaudrateNotSupport:
689 case kStatus_InvalidArgument:
690 return Status::InvalidArgument();
691 case kStatus_Success:
692 return OkStatus();
693 }
694 }
695
DoSetFlowControl(bool enable)696 Status DmaUartMcuxpressoNonBlocking::DoSetFlowControl(bool enable) {
697 config_.flow_control = enable;
698
699 if (initialized_) {
700 USART_EnableCTS(config_.usart_base, enable);
701 }
702
703 return OkStatus();
704 }
705
CompleteFlushRequest(Status status)706 bool DmaUartMcuxpressoNonBlocking::CompleteFlushRequest(Status status) {
707 if (!tx_data_.flush_request.valid) {
708 return false;
709 }
710
711 tx_data_.flush_request.callback(status);
712 tx_data_.flush_request.valid = false;
713 tx_data_.flush_request.callback = nullptr;
714
715 PW_DCHECK(USART_FIFOSTAT_TXEMPTY(config_.usart_base->FIFOSTAT));
716
717 return true;
718 }
719
DoFlushOutput(Function<void (Status status)> && callback)720 Status DmaUartMcuxpressoNonBlocking::DoFlushOutput(
721 Function<void(Status status)>&& callback) {
722 std::lock_guard lock(interrupt_lock_);
723
724 if (tx_data_.flush_request.valid) {
725 return Status::FailedPrecondition();
726 }
727
728 if (!tx_data_.request.valid) {
729 callback(OkStatus());
730 return OkStatus();
731 }
732
733 tx_data_.flush_request.callback = std::move(callback);
734 tx_data_.flush_request.valid = true;
735
736 return OkStatus();
737 }
738
DoCancelFlushOutput()739 bool DmaUartMcuxpressoNonBlocking::DoCancelFlushOutput() {
740 std::lock_guard lock(interrupt_lock_);
741 return CompleteFlushRequest(Status::Cancelled());
742 }
743
744 } // namespace pw::uart
745