• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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