ove/async_runtime/uart.rs
1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! Async wrapper around [`crate::Uart`] using `ove_uart_set_rx_notify`,
8//! which delegates to the underlying RX stream's notify hook.
9//!
10//! The UART driver pushes received bytes into an internal `ove_stream`
11//! from ISR context via `ove_uart_rx_isr_push`. Registering a notify
12//! callback there is exactly what async RX needs: each pushed byte
13//! triggers the trampoline, which wakes the embassy task suspended on
14//! `read().await`.
15//!
16//! TX is left synchronous — `Uart::write` already blocks until the
17//! requested bytes are accepted into the HW FIFO, and exposing a true
18//! async TX would need a separate TX-completion DMA callback path
19//! (Phase 3 territory).
20
21use core::future::poll_fn;
22use core::task::Poll;
23use core::time::Duration;
24
25use embassy_sync::waitqueue::AtomicWaker;
26
27use crate::error::{Error, Result};
28use crate::uart::Uart;
29
30/// Async wrapper around an [`ove::Uart`](crate::Uart).
31///
32/// Same lifetime contract as the other `Async*` wrappers: methods take
33/// `&'static self` because the C-side notify callback retains a
34/// pointer to the internal `AtomicWaker`.
35pub struct AsyncUart {
36 inner: Uart,
37 waker: AtomicWaker,
38}
39
40// SAFETY: `AsyncUart` wraps a `Uart` (whose own Send/Sync reflects the
41// substrate's tx/rx serialisation) plus an `AtomicWaker`.
42unsafe impl Send for AsyncUart {}
43unsafe impl Sync for AsyncUart {}
44
45impl AsyncUart {
46 pub const fn new(inner: Uart) -> Self {
47 Self {
48 inner,
49 waker: AtomicWaker::new(),
50 }
51 }
52
53 /// Register the C-side notify callback. Must be called exactly once
54 /// after the wrapper reaches its final 'static location.
55 pub fn arm(&'static self) -> Result<()> {
56 // SAFETY: 'static wrapper, AtomicWaker pinned for program
57 // lifetime; trampoline performs an ISR-safe AtomicWaker::wake.
58 unsafe {
59 self.inner.set_rx_notify(
60 Some(uart_notify_trampoline),
61 &self.waker as *const AtomicWaker as *mut core::ffi::c_void,
62 )
63 }
64 }
65
66 /// Async read — awaits until at least one byte is available, then
67 /// returns the number of bytes copied.
68 ///
69 /// Returns `Ok(0)` only if `buf.is_empty()`.
70 pub async fn read(&'static self, buf: &mut [u8]) -> Result<usize> {
71 if buf.is_empty() {
72 return Ok(0);
73 }
74 poll_fn(|cx| {
75 // Fast path: try a non-blocking read first.
76 match self.inner.read(buf, Duration::ZERO) {
77 Ok(n) if n > 0 => return Poll::Ready(Ok(n)),
78 Ok(_) | Err(Error::WouldBlock) | Err(Error::Timeout) => {}
79 Err(e) => return Poll::Ready(Err(e)),
80 }
81 // Register + recheck.
82 self.waker.register(cx.waker());
83 match self.inner.read(buf, Duration::ZERO) {
84 Ok(n) if n > 0 => Poll::Ready(Ok(n)),
85 Ok(_) | Err(Error::WouldBlock) | Err(Error::Timeout) => Poll::Pending,
86 Err(e) => Poll::Ready(Err(e)),
87 }
88 })
89 .await
90 }
91
92 /// Borrow the underlying [`Uart`] for synchronous operations
93 /// (`write`, `bytes_available`, `flush`).
94 #[inline]
95 pub fn inner(&self) -> &Uart {
96 &self.inner
97 }
98}
99
100unsafe extern "C" fn uart_notify_trampoline(user_data: *mut core::ffi::c_void) {
101 let waker = unsafe { &*(user_data as *const AtomicWaker) };
102 waker.wake();
103}