Skip to main content

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}