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.h"
16
17 #include <optional>
18
19 #include "fsl_flexcomm.h"
20 #include "pw_assert/check.h"
21 #include "pw_preprocessor/util.h"
22
23 namespace pw::uart {
24
25 // Deinitialize the DMA channels and USART.
Deinit()26 void DmaUartMcuxpresso::Deinit() {
27 if (!initialized_) {
28 return;
29 }
30
31 config_.tx_dma_ch.Disable();
32 config_.rx_dma_ch.Disable();
33
34 USART_Deinit(config_.usart_base);
35 clock_tree_element_controller_.Release().IgnoreError();
36 initialized_ = false;
37 }
38
~DmaUartMcuxpresso()39 DmaUartMcuxpresso::~DmaUartMcuxpresso() { Deinit(); }
40
41 // Initialize the USART and DMA channels based on the configuration
42 // specified during object creation.
Init()43 Status DmaUartMcuxpresso::Init() {
44 if (config_.usart_base == nullptr) {
45 return Status::InvalidArgument();
46 }
47 if (config_.baud_rate == 0) {
48 return Status::InvalidArgument();
49 }
50
51 usart_config_t defconfig;
52 USART_GetDefaultConfig(&defconfig);
53
54 defconfig.baudRate_Bps = config_.baud_rate;
55 defconfig.enableHardwareFlowControl = config_.flow_control;
56 defconfig.parityMode = config_.parity;
57 defconfig.enableTx = true;
58 defconfig.enableRx = true;
59
60 PW_TRY(clock_tree_element_controller_.Acquire());
61 flexcomm_clock_freq_ =
62 CLOCK_GetFlexcommClkFreq(FLEXCOMM_GetInstance(config_.usart_base));
63 status_t status =
64 USART_Init(config_.usart_base, &defconfig, flexcomm_clock_freq_);
65 if (status != kStatus_Success) {
66 clock_tree_element_controller_.Release().IgnoreError();
67 return Status::Internal();
68 }
69
70 // We need to touch register space that can be shared
71 // among several DMA peripherals, hence we need to access
72 // it exclusively. We achieve exclusive access on non-SMP systems as
73 // a side effect of acquiring the interrupt_lock_, since acquiring the
74 // interrupt_lock_ disables interrupts on the current CPU, which means
75 // we cannot get descheduled until we release the interrupt_lock_.
76 interrupt_lock_.lock();
77
78 // Temporarily enable clock to inputmux, so that RX and TX DMA requests can
79 // get enabled.
80 INPUTMUX_Init(INPUTMUX);
81 INPUTMUX_EnableSignal(
82 INPUTMUX, config_.rx_input_mux_dmac_ch_request_en, true);
83 INPUTMUX_EnableSignal(
84 INPUTMUX, config_.tx_input_mux_dmac_ch_request_en, true);
85 INPUTMUX_Deinit(INPUTMUX);
86
87 interrupt_lock_.unlock();
88
89 config_.tx_dma_ch.Enable();
90 config_.rx_dma_ch.Enable();
91
92 // Initialized enough for Deinit code to handle any errors from here.
93 initialized_ = true;
94
95 status = USART_TransferCreateHandleDMA(config_.usart_base,
96 &uart_dma_handle_,
97 TxRxCompletionCallback,
98 this,
99 config_.tx_dma_ch.handle(),
100 config_.rx_dma_ch.handle());
101
102 if (status != kStatus_Success) {
103 Deinit();
104 return Status::Internal();
105 }
106
107 // Read into the rx ring buffer.
108 interrupt_lock_.lock();
109 TriggerReadDma();
110 interrupt_lock_.unlock();
111
112 return OkStatus();
113 }
114
115 // DMA usart data into ring buffer
116 //
117 // At most kUsartDmaMaxTransferCount bytes can be copied per DMA transfer.
118 // If completion_size is specified and dataSize is larger than completion_size,
119 // the dataSize will be limited to completion_size so that the completion
120 // callback will be called once completion_size bytes have been received.
TriggerReadDma()121 void DmaUartMcuxpresso::TriggerReadDma() {
122 uint8_t* ring_buffer =
123 reinterpret_cast<uint8_t*>(rx_data_.ring_buffer.data());
124 rx_data_.transfer.data = &ring_buffer[rx_data_.ring_buffer_write_idx];
125
126 if (rx_data_.ring_buffer_write_idx + kUsartDmaMaxTransferCount >
127 rx_data_.ring_buffer.size_bytes()) {
128 rx_data_.transfer.dataSize =
129 rx_data_.ring_buffer.size_bytes() - rx_data_.ring_buffer_write_idx;
130 } else {
131 rx_data_.transfer.dataSize = kUsartDmaMaxTransferCount;
132 }
133
134 if (rx_data_.completion_size > 0 &&
135 rx_data_.transfer.dataSize > rx_data_.completion_size) {
136 // Completion callback will be called once this transfer completes.
137 rx_data_.transfer.dataSize = rx_data_.completion_size;
138 }
139
140 USART_TransferReceiveDMA(
141 config_.usart_base, &uart_dma_handle_, &rx_data_.transfer);
142 }
143
144 // DMA send buffer data
TriggerWriteDma()145 void DmaUartMcuxpresso::TriggerWriteDma() {
146 const uint8_t* tx_buffer =
147 reinterpret_cast<const uint8_t*>(tx_data_.buffer.data());
148 tx_data_.transfer.txData = &tx_buffer[tx_data_.tx_idx];
149 if (tx_data_.tx_idx + kUsartDmaMaxTransferCount >
150 tx_data_.buffer.size_bytes()) {
151 // Completion callback will be called once this transfer completes.
152 tx_data_.transfer.dataSize = tx_data_.buffer.size_bytes() - tx_data_.tx_idx;
153 } else {
154 tx_data_.transfer.dataSize = kUsartDmaMaxTransferCount;
155 }
156
157 USART_TransferSendDMA(
158 config_.usart_base, &uart_dma_handle_, &tx_data_.transfer);
159 }
160
161 // Completion callback for TX and RX transactions
TxRxCompletionCallback(USART_Type *,usart_dma_handle_t *,status_t status,void * param)162 void DmaUartMcuxpresso::TxRxCompletionCallback(USART_Type* /* base */,
163 usart_dma_handle_t* /* state */,
164 status_t status,
165 void* param) {
166 DmaUartMcuxpresso* stream = reinterpret_cast<DmaUartMcuxpresso*>(param);
167
168 if (status == kStatus_USART_RxIdle) {
169 // RX transfer
170
171 // Acquire the interrupt_lock_ to ensure that on SMP systems
172 // access to the rx_data is synchronized.
173 stream->interrupt_lock_.lock();
174
175 struct UsartDmaRxData* rx_data = &stream->rx_data_;
176 rx_data->ring_buffer_write_idx += rx_data->transfer.dataSize;
177 rx_data->data_received += rx_data->transfer.dataSize;
178
179 PW_DCHECK_INT_LE(rx_data->ring_buffer_write_idx,
180 rx_data->ring_buffer.size_bytes());
181 if (rx_data->ring_buffer_write_idx == rx_data->ring_buffer.size_bytes()) {
182 rx_data->ring_buffer_write_idx = 0;
183 }
184
185 bool notify_rx_completion = false;
186 if (rx_data->completion_size > 0) {
187 PW_DCHECK_INT_GE(rx_data->completion_size, rx_data->transfer.dataSize);
188 rx_data->completion_size -= rx_data->transfer.dataSize;
189 if (rx_data->completion_size == 0) {
190 // We have satisfied the receive request, we must wake up the receiver.
191 // Before we can issue the wake up, we must trigger the next DMA read
192 // operation, since the notification might yield the CPU.
193 notify_rx_completion = true;
194 }
195 }
196 stream->TriggerReadDma();
197
198 stream->interrupt_lock_.unlock();
199
200 if (notify_rx_completion) {
201 rx_data->notification.release();
202 }
203 } else if (status == kStatus_USART_TxIdle) {
204 // Tx transfer
205 UsartDmaTxData* tx_data = &stream->tx_data_;
206 tx_data->tx_idx += tx_data->transfer.dataSize;
207 if (tx_data->tx_idx == tx_data->buffer.size_bytes()) {
208 // We have completed the send request, we must wake up the sender.
209 tx_data->notification.release();
210 } else {
211 PW_CHECK_INT_LT(tx_data->tx_idx, tx_data->buffer.size_bytes());
212 stream->TriggerWriteDma();
213 }
214 }
215 }
216
217 // Get the amount of bytes that have been received, but haven't been copied yet
218 //
219 // Note: The caller must ensure that the interrupt handler cannot execute.
TransferGetReceiveDMACountLockHeld()220 StatusWithSize DmaUartMcuxpresso::TransferGetReceiveDMACountLockHeld() {
221 uint32_t count = 0;
222
223 // If no in-flight transfer is in progress, there is no pending data
224 // available. We have initialized count to 0 to account for that.
225 (void)USART_TransferGetReceiveCountDMA(
226 config_.usart_base, &uart_dma_handle_, &count);
227
228 // We must be executing with the interrupt_lock_ held, so that the interrupt
229 // handler cannot change data_received.
230 count += rx_data_.data_received - rx_data_.data_copied;
231 // Check whether we hit an overflow condition
232 if (count > rx_data_.ring_buffer.size_bytes()) {
233 return StatusWithSize(Status::DataLoss(), 0);
234 }
235 return StatusWithSize(count);
236 }
237
238 // Get the amount of bytes that have been received, but haven't been copied yet
TransferGetReceiveDMACount()239 StatusWithSize DmaUartMcuxpresso::TransferGetReceiveDMACount() {
240 // We need to acquire the interrupt_lock_ , so that the interrupt handler
241 // cannot run to change rxRingBufferWriteIdx.
242 interrupt_lock_.lock();
243 StatusWithSize status = TransferGetReceiveDMACountLockHeld();
244 interrupt_lock_.unlock();
245 return status;
246 }
247
248 // Get the amount of bytes that have not been yet received for the current
249 // transfer
250 //
251 // Note: This function may only be called once the RX transaction has been
252 // aborted.
GetReceiveTransferRemainingBytes()253 size_t DmaUartMcuxpresso::GetReceiveTransferRemainingBytes() {
254 return DMA_GetRemainingBytes(uart_dma_handle_.rxDmaHandle->base,
255 uart_dma_handle_.rxDmaHandle->channel);
256 }
257
258 // Wait for more receive bytes to arrive to satisfy request
259 //
260 // Once we have acquired the interrupt_lock_, we check whether we can
261 // satisfy the request, and if not, we will abort the current
262 // transaction if the current transaction will be able to satisfy
263 // the outstanding request. Once the transaction has been aborted
264 // we can specify the completion_size, so that the completion callback
265 // can wake us up when the bytes_needed bytes have been received.
266 //
267 // If more than one transaction is required to satisfy the request,
268 // we don't need to abort the transaction and instead can leverage
269 // the fact that the completion callback won't be triggered since we
270 // have acquired the interrupt_lock_ . This allows us to specify
271 // the completion_size that will be seen by the completion callback
272 // when it executes. A subsequent completion callback will wake us up
273 // when the bytes_needed have been received.
WaitForReceiveBytes(size_t bytes_needed,std::optional<chrono::SystemClock::time_point> deadline)274 Status DmaUartMcuxpresso::WaitForReceiveBytes(
275 size_t bytes_needed,
276 std::optional<chrono::SystemClock::time_point> deadline) {
277 // Acquire the interrupt_lock_, so that the interrupt handler cannot
278 // execute and modify the shared state.
279 interrupt_lock_.lock();
280
281 // Recheck what the current amount of available bytes is.
282 StatusWithSize rx_count_status = TransferGetReceiveDMACountLockHeld();
283 if (!rx_count_status.ok()) {
284 interrupt_lock_.unlock();
285 return rx_count_status.status();
286 }
287
288 size_t rx_count = rx_count_status.size();
289 if (rx_count >= bytes_needed) {
290 interrupt_lock_.unlock();
291 return OkStatus();
292 }
293
294 // Not enough bytes available yet.
295 // We check whether more bytes are needed than the transfer's
296 // dataSize, which means that at least one more transfer must
297 // complete to satisfy this receive request.
298 size_t pos_in_transfer =
299 rx_data_.data_copied + rx_count - rx_data_.data_received;
300 PW_DCHECK_INT_LE(pos_in_transfer, rx_data_.transfer.dataSize);
301
302 size_t transfer_bytes_needed =
303 bytes_needed + rx_data_.data_copied - rx_data_.data_received;
304 bool aborted = false;
305
306 if (transfer_bytes_needed < rx_data_.transfer.dataSize) {
307 // Abort the current transfer, so that we can schedule a receive
308 // transfer to satisfy this request.
309 USART_TransferAbortReceiveDMA(config_.usart_base, &uart_dma_handle_);
310 size_t remaining_transfer_bytes = GetReceiveTransferRemainingBytes();
311 if (remaining_transfer_bytes == 0) {
312 // We have received all bytes for the current transfer, we will
313 // restart the loop in the caller's context.
314 // The interrupt handler will execute and call TriggerReadDma
315 // to schedule the next receive DMA transfer.
316 interrupt_lock_.unlock();
317 return OkStatus();
318 }
319 // We have successfully aborted an in-flight transfer. No interrupt
320 // callback will be called for it.
321 aborted = true;
322 // We need to fix up the transfer size for the aborted transfer.
323 rx_data_.transfer.dataSize -= remaining_transfer_bytes;
324 } else {
325 // We require at least as much data as provided by the current
326 // transfer. We know that this code cannot execute while the
327 // receive transaction isn't active, so we know that the
328 // completion callback will still execute.
329 }
330
331 // Tell the transfer callback when to deliver the completion
332 // notification.
333 rx_data_.completion_size = transfer_bytes_needed;
334
335 // Since a caller could request a receive amount that exceeds the ring
336 // buffer size, we must cap the rxCompletionSize. In addition, we
337 // don't want that the rxRingBuffer overflows, so we cap the
338 // rxCompletionSize to 25% of the ringBufferSize to ensure that the
339 // ring buffer gets drained frequently enough.
340 if (rx_data_.completion_size >
341 rx_data_.ring_buffer.size_bytes() / kUsartRxRingBufferSplitCount) {
342 rx_data_.completion_size =
343 rx_data_.ring_buffer.size_bytes() / kUsartRxRingBufferSplitCount;
344 }
345
346 interrupt_lock_.unlock();
347
348 if (aborted) {
349 // We have received data, but we haven't accounted for it, since the
350 // callback won't execute due to the abort. Execute the callback
351 // from here instead. Since the DMA transfer has been aborted, and
352 // the available data isn't sufficient to satisfy this request, the
353 // next receive DMA transfer will unblock this thread.
354 TxRxCompletionCallback(
355 config_.usart_base, &uart_dma_handle_, kStatus_USART_RxIdle, this);
356 }
357
358 // Wait for the interrupt handler to deliver the completion
359 // notification.
360 Status status = OkStatus();
361 if (deadline.has_value()) {
362 if (!rx_data_.notification.try_acquire_until(*deadline)) {
363 // Timeout expired. No need to cancel the DMA; subsequent bytes will
364 // just go into the ring buffer.
365 status.Update(Status::DeadlineExceeded());
366 }
367 } else {
368 rx_data_.notification.acquire();
369 }
370 // We have received bytes that can be copied out, we will restart
371 // the loop in the caller's context.
372 return status;
373 }
374
375 // Copy the data from the receive ring buffer into the destination data buffer
CopyReceiveData(ByteBuilder & bb,size_t copy_size)376 void DmaUartMcuxpresso::CopyReceiveData(ByteBuilder& bb, size_t copy_size) {
377 ByteSpan ring_buffer = rx_data_.ring_buffer;
378 // Check whether we need to perform a wrap around copy operation or end
379 // right at the end of the buffer.
380 if (rx_data_.ring_buffer_read_idx + copy_size >=
381 rx_data_.ring_buffer.size_bytes()) {
382 size_t first_copy_size =
383 rx_data_.ring_buffer.size_bytes() - rx_data_.ring_buffer_read_idx;
384 bb.append(
385 ring_buffer.subspan(rx_data_.ring_buffer_read_idx, first_copy_size));
386 size_t second_copy_size = copy_size - first_copy_size;
387 // Source buffer is at offset 0.
388 bb.append(ring_buffer.subspan(0, second_copy_size));
389 rx_data_.ring_buffer_read_idx = second_copy_size;
390 } else {
391 // Normal copy operation
392 PW_DCHECK_INT_LT(rx_data_.ring_buffer_read_idx + copy_size,
393 rx_data_.ring_buffer.size_bytes());
394 bb.append(ring_buffer.subspan(rx_data_.ring_buffer_read_idx, copy_size));
395 rx_data_.ring_buffer_read_idx += copy_size;
396 }
397 rx_data_.data_copied += copy_size;
398 }
399
DoEnable(bool enable)400 Status DmaUartMcuxpresso::DoEnable(bool enable) {
401 if (enable == initialized_) {
402 return OkStatus();
403 }
404
405 if (enable) {
406 return Init();
407 } else {
408 Deinit();
409 return OkStatus();
410 }
411 }
412
DoSetBaudRate(uint32_t baud_rate)413 Status DmaUartMcuxpresso::DoSetBaudRate(uint32_t baud_rate) {
414 if (baud_rate == 0) {
415 return Status::InvalidArgument();
416 }
417
418 config_.baud_rate = baud_rate;
419
420 if (!initialized_) {
421 return OkStatus();
422 }
423
424 status_t status = USART_SetBaudRate(
425 config_.usart_base, config_.baud_rate, flexcomm_clock_freq_);
426 switch (status) {
427 default:
428 return Status::Unknown();
429 case kStatus_USART_BaudrateNotSupport:
430 case kStatus_InvalidArgument:
431 return Status::InvalidArgument();
432 case kStatus_Success:
433 return OkStatus();
434 }
435 }
436
DoSetFlowControl(bool enable)437 Status DmaUartMcuxpresso::DoSetFlowControl(bool enable) {
438 config_.flow_control = enable;
439
440 if (!initialized_) {
441 return OkStatus();
442 }
443
444 USART_EnableCTS(config_.usart_base, enable);
445 return OkStatus();
446 }
447
448 // Copy data from the RX ring buffer into the caller provided buffer
449 //
450 // If the ring buffer can already satisfy the read request, the
451 // data will be copied from the ring buffer into the provided buffer.
452 // If no data, or not sufficient data is available to satisfy the
453 // read request, the caller will wait for the completion callback to
454 // signal that data is available and can be copied from the ring buffer
455 // to the provided buffer.
456 //
457 // Note: A reader may request to read more data than can be stored
458 // inside the RX ring buffer.
459 //
460 // Note: Only one thread should be calling this function,
461 // otherwise DoRead calls might fail due to contention for
462 // the USART RX channel.
DoTryReadFor(ByteSpan rx_buffer,size_t min_bytes,std::optional<chrono::SystemClock::duration> timeout)463 StatusWithSize DmaUartMcuxpresso::DoTryReadFor(
464 ByteSpan rx_buffer,
465 size_t min_bytes,
466 std::optional<chrono::SystemClock::duration> timeout) {
467 if (timeout.has_value() && *timeout < chrono::SystemClock::duration::zero()) {
468 return StatusWithSize(Status::InvalidArgument(), 0);
469 }
470
471 size_t length = rx_buffer.size();
472 if (length == 0 || min_bytes > length) {
473 return StatusWithSize(Status::InvalidArgument(), 0);
474 }
475
476 std::optional<chrono::SystemClock::time_point> deadline = std::nullopt;
477 if (timeout.has_value()) {
478 deadline.emplace(*timeout + chrono::SystemClock::now());
479 }
480
481 // We only allow a single thread to read from the USART at a time.
482 bool was_busy = rx_data_.busy.exchange(true);
483 if (was_busy) {
484 return StatusWithSize(Status::FailedPrecondition(), 0);
485 }
486
487 Status status = OkStatus();
488
489 ByteBuilder bb(rx_buffer);
490 size_t bytes_copied = 0;
491 while (bytes_copied < min_bytes) {
492 // Get the number of bytes available to read.
493 StatusWithSize rx_count_status = TransferGetReceiveDMACount();
494 if (!rx_count_status.ok()) {
495 status.Update(rx_count_status.status());
496 break;
497 }
498 size_t rx_count = rx_count_status.size();
499
500 // Copy available bytes out of the ring buffer.
501 if (rx_count > 0) {
502 // Allow copying more than min_bytes if they are available.
503 size_t copy_size = std::min(length - bytes_copied, rx_count);
504 CopyReceiveData(bb, copy_size);
505 bytes_copied += copy_size;
506 }
507
508 // Do we still need more bytes?
509 // We need to wait for more DMA bytes if so.
510 if (bytes_copied < min_bytes) {
511 // Check if the timeout has expired, if applicable.
512 if (deadline.has_value() && chrono::SystemClock::now() >= *deadline) {
513 status.Update(Status::DeadlineExceeded());
514 break;
515 }
516
517 // Wait up to the timeout duration to receive more bytes.
518 Status wait_status =
519 WaitForReceiveBytes(min_bytes - bytes_copied, deadline);
520 // Even if we exceeded the deadline, stay in the loop for one more
521 // iteration to copy out any final bytes.
522 if (!wait_status.ok() && !wait_status.IsDeadlineExceeded()) {
523 status.Update(wait_status);
524 break;
525 }
526
527 // At this point, we have new bytes to read, have timed out, or both.
528 // Go back to the top of the loop to figure out which.
529 }
530 }
531
532 rx_data_.busy.store(false);
533 return StatusWithSize(status, bytes_copied);
534 }
535
536 // Write data to USART using DMA transactions
537 //
538 // Note: Only one thread should be calling this function,
539 // otherwise DoWrite calls might fail due to contention for
540 // the USART TX channel.
DoTryWriteFor(ConstByteSpan tx_buffer,std::optional<chrono::SystemClock::duration> timeout)541 StatusWithSize DmaUartMcuxpresso::DoTryWriteFor(
542 ConstByteSpan tx_buffer,
543 std::optional<chrono::SystemClock::duration> timeout) {
544 if (tx_buffer.size() == 0) {
545 return StatusWithSize(0);
546 }
547
548 bool was_busy = tx_data_.busy.exchange(true);
549 if (was_busy) {
550 // Another thread is already transmitting data.
551 return StatusWithSize::FailedPrecondition(0);
552 }
553
554 // Start the DMA. If multiple DMA transactions are needed, the completion
555 // callback will set them up.
556 tx_data_.buffer = tx_buffer;
557 tx_data_.tx_idx = 0;
558
559 TriggerWriteDma();
560
561 // Wait for completion, optionally with timeout.
562 Status status = OkStatus();
563 if (timeout.has_value()) {
564 if (!tx_data_.notification.try_acquire_for(*timeout)) {
565 interrupt_lock_.lock();
566 USART_TransferAbortSendDMA(config_.usart_base, &uart_dma_handle_);
567 interrupt_lock_.unlock();
568 status.Update(Status::DeadlineExceeded());
569 }
570 } else {
571 tx_data_.notification.acquire();
572 }
573
574 size_t bytes_written = tx_data_.tx_idx;
575
576 tx_data_.busy.store(false);
577
578 return StatusWithSize(status, bytes_written);
579 }
580
DoConservativeReadAvailable()581 size_t DmaUartMcuxpresso::DoConservativeReadAvailable() {
582 StatusWithSize status = TransferGetReceiveDMACount();
583 if (!status.ok()) {
584 return 0;
585 }
586 return status.size();
587 }
588
DoFlushOutput()589 Status DmaUartMcuxpresso::DoFlushOutput() {
590 // Unsupported
591 return OkStatus();
592 }
593
DoClearPendingReceiveBytes()594 Status DmaUartMcuxpresso::DoClearPendingReceiveBytes() {
595 bool was_busy = rx_data_.busy.exchange(true);
596 if (was_busy) {
597 return Status::FailedPrecondition();
598 }
599 size_t bytes_pending = rx_data_.data_received - rx_data_.data_copied;
600 if (rx_data_.ring_buffer_read_idx + bytes_pending >=
601 rx_data_.ring_buffer.size_bytes()) {
602 rx_data_.ring_buffer_read_idx -= rx_data_.ring_buffer.size_bytes();
603 }
604 rx_data_.ring_buffer_read_idx += bytes_pending;
605
606 rx_data_.data_copied = rx_data_.data_received;
607 rx_data_.busy.store(false);
608
609 return OkStatus();
610 }
611
612 } // namespace pw::uart
613