xref: /aosp_15_r20/external/pigweed/pw_uart_mcuxpresso/dma_uart.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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