Skip to main content

axi_uartlite/
tx_async.rs

1//! # Asynchronous TX support.
2//!
3//! This module provides support for asynchronous non-blocking TX transfers.
4//!
5//! It provides a static number of async wakers to allow a configurable amount of pollable
6//! [TxFuture]s. Each UARTLite [Tx] instance which performs asynchronous TX operations needs
7//! to be to explicitely assigned a waker when creating an awaitable [TxAsync] structure.
8//! Retrieve the resulting [TxToken] via [TxAsync::token] right after construction and pass it
9//! to [on_interrupt_tx] from your interrupt handler.
10//!
11//! The maximum number of available wakers is configured via the waker feature flags:
12//!
13//! - `1-waker`
14//! - `2-wakers`
15//! - `4-wakers`
16//! - `8-wakers`
17//! - `16-wakers`
18//! - `32-wakers`
19#[cfg(not(feature = "portable-atomic"))]
20use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
21use core::{convert::Infallible, marker::PhantomData};
22use embassy_sync::waitqueue::AtomicWaker;
23#[cfg(feature = "portable-atomic")]
24use portable_atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
25
26use crate::{FIFO_DEPTH, Tx};
27
28/// 1 waker (default).
29#[cfg(feature = "1-waker")]
30pub const NUM_WAKERS: usize = 1;
31/// 2 wakers
32#[cfg(feature = "2-wakers")]
33pub const NUM_WAKERS: usize = 2;
34/// 4 wakers
35#[cfg(feature = "4-wakers")]
36pub const NUM_WAKERS: usize = 4;
37/// 8 wakers
38#[cfg(feature = "8-wakers")]
39pub const NUM_WAKERS: usize = 8;
40/// 16 wakers
41#[cfg(feature = "16-wakers")]
42pub const NUM_WAKERS: usize = 16;
43/// 32 wakers
44#[cfg(feature = "32-wakers")]
45pub const NUM_WAKERS: usize = 32;
46static UART_TX_WAKERS: [AtomicWaker; NUM_WAKERS] = [const { AtomicWaker::new() }; NUM_WAKERS];
47static TX_CONTEXTS: [TxContext; NUM_WAKERS] = [const { TxContext::new() }; NUM_WAKERS];
48// Completion flag. Kept outside of the context structure as an atomic to avoid
49// critical section.
50static TX_DONE: [AtomicBool; NUM_WAKERS] = [const { AtomicBool::new(false) }; NUM_WAKERS];
51
52/// Invalid waker index for [NUM_WAKERS].
53#[derive(Debug, thiserror::Error)]
54#[cfg_attr(feature = "defmt", derive(defmt::Format))]
55#[error("invalid waker slot index: {0}")]
56pub struct InvalidWakerIndex(pub usize);
57
58/// Identifies a [TxAsync] driver's UART instance and waker slot, e.g. for use in an interrupt
59/// handler.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61#[cfg_attr(feature = "defmt", derive(defmt::Format))]
62pub struct TxToken {
63    base_addr: usize,
64    waker_idx: usize,
65}
66
67impl TxToken {
68    /// The UART register block's base address.
69    #[inline]
70    pub fn base_addr(&self) -> usize {
71        self.base_addr
72    }
73
74    /// The waker slot this token's TX driver was constructed with.
75    #[inline]
76    pub fn waker_idx(&self) -> usize {
77        self.waker_idx
78    }
79
80    /// Constructs a token from a raw base address and waker index, e.g. for use in an interrupt
81    /// handler that only has these two values available from static configuration, rather than
82    /// a token retrieved via [TxAsync::token].
83    ///
84    /// # Safety
85    ///
86    /// The caller must ensure `base_addr` is the real base address of the UART register block
87    /// whose TX interrupt is being serviced, and that `waker_idx` matches the slot originally
88    /// passed to the corresponding [TxAsync::new] call.
89    #[inline]
90    pub const unsafe fn steal(base_addr: usize, waker_idx: usize) -> Self {
91        Self {
92            base_addr,
93            waker_idx,
94        }
95    }
96}
97
98/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given
99/// UART peripheral.
100///
101/// The user has to call this once in the interrupt handler responsible if the interrupt was
102/// triggered by the UARTLite using [TxAsync]. `token` should be retrieved once via
103/// [TxAsync::token] right after constructing the driver.
104///
105/// # Safety
106///
107/// `token` must have been returned by [TxAsync::token] (or constructed via [TxToken::steal] to
108/// match) for a TX driver actually performing the transfer being serviced.
109pub unsafe fn on_interrupt_tx(token: &TxToken) {
110    if token.waker_idx >= NUM_WAKERS {
111        return;
112    }
113    let waker_slot = token.waker_idx;
114    let mut uartlite_tx = unsafe { Tx::steal(token.base_addr) };
115    let status = uartlite_tx.regs.read_stat_reg();
116    // Interrupt are not even enabled.
117    if !status.intr_enabled() {
118        return;
119    }
120    let context = &TX_CONTEXTS[waker_slot];
121    // `Acquire` pairs with the `Release` store in `TxFuture::new`/`poll`/`Drop`: seeing a
122    // non-null pointer here guarantees `transfer_len`/`progress` below are the values published
123    // together with it, not stale ones from a previous transfer.
124    let raw_data_ptr = context.raw_data.load(Ordering::Acquire) as *const u8;
125    // No transfer active.
126    if raw_data_ptr.is_null() {
127        return;
128    }
129    let slice_len = context.transfer_len.load(Ordering::Relaxed);
130    let mut progress = context.progress.load(Ordering::Relaxed);
131    // Safety: We documented that the user provided slice must outlive the future, so we convert
132    // the raw pointer back to the slice here.
133    let slice = unsafe { core::slice::from_raw_parts(raw_data_ptr, slice_len) };
134    if (progress >= slice_len && status.tx_fifo_empty()) || slice_len == 0 {
135        // Transfer is done. `Release` publishes the final `progress` value (and any FIFO writes
136        // above) to whichever context observes `TX_DONE` via the `Acquire` swap in `poll`.
137        TX_DONE[waker_slot].store(true, core::sync::atomic::Ordering::Release);
138        UART_TX_WAKERS[waker_slot].wake();
139        return;
140    }
141    while progress < slice_len {
142        if uartlite_tx.regs.read_stat_reg().tx_fifo_full() {
143            break;
144        }
145        // Safety: TX structure is owned by the future which does not write into the the data
146        // register, so we can assume we are the only one writing to the data register.
147        uartlite_tx.write_fifo_unchecked(slice[progress]);
148        progress += 1;
149    }
150    context.progress.store(progress, Ordering::Relaxed);
151}
152
153/// TX context structure.
154#[derive(Debug)]
155pub struct TxContext {
156    progress: AtomicUsize,
157    raw_data: AtomicPtr<u8>,
158    transfer_len: AtomicUsize,
159}
160
161#[allow(clippy::new_without_default)]
162impl TxContext {
163    /// Create a new TX context structure.
164    pub const fn new() -> Self {
165        Self {
166            progress: AtomicUsize::new(0),
167            raw_data: AtomicPtr::new(core::ptr::null_mut()),
168            transfer_len: AtomicUsize::new(0),
169        }
170    }
171}
172
173/// TX future structure.
174pub struct TxFuture<'tx, 'buf> {
175    waker_idx: usize,
176    tx: &'tx mut TxAsync,
177    // Set once `poll` observes completion. `TX_DONE` itself is not enough to tell completion
178    // and cancellation apart in `Drop`, because `poll` already swaps it back to `false` as
179    // part of observing it.
180    completed: bool,
181    phantom: core::marker::PhantomData<&'buf ()>,
182}
183
184impl<'tx, 'buf> TxFuture<'tx, 'buf> {
185    /// Create a new TX future which can be used for asynchronous TX operations.
186    pub fn new(
187        tx: &'tx mut TxAsync,
188        waker_idx: usize,
189        data: &'buf [u8],
190    ) -> Result<Self, InvalidWakerIndex> {
191        TX_DONE[waker_idx].store(false, core::sync::atomic::Ordering::Relaxed);
192        tx.tx.reset_fifo();
193
194        let init_fill_count = core::cmp::min(data.len(), FIFO_DEPTH);
195        let context_ref = &TX_CONTEXTS[waker_idx];
196        // Publish the guarded fields before opening the gate (`raw_data`) with `Release`, so a
197        // reader that observes `raw_data` non-null via the `Acquire` load in `on_interrupt_tx`
198        // is guaranteed to see these too, rather than stale values from a previous transfer.
199        context_ref
200            .transfer_len
201            .store(data.len(), Ordering::Relaxed);
202        context_ref
203            .progress
204            .store(init_fill_count, Ordering::Relaxed);
205        context_ref
206            .raw_data
207            .store(data.as_ptr() as *mut u8, Ordering::Release);
208        // We fill the FIFO with initial data.
209        for data in data.iter().take(init_fill_count) {
210            tx.tx.write_fifo_unchecked(*data);
211        }
212
213        Ok(Self {
214            waker_idx,
215            tx,
216            completed: false,
217            phantom: PhantomData,
218        })
219    }
220}
221
222impl Future for TxFuture<'_, '_> {
223    type Output = usize;
224
225    fn poll(
226        mut self: core::pin::Pin<&mut Self>,
227        cx: &mut core::task::Context<'_>,
228    ) -> core::task::Poll<Self::Output> {
229        UART_TX_WAKERS[self.waker_idx].register(cx.waker());
230        if TX_DONE[self.waker_idx].swap(false, core::sync::atomic::Ordering::Acquire) {
231            let context = &TX_CONTEXTS[self.waker_idx];
232            context
233                .raw_data
234                .store(core::ptr::null_mut(), Ordering::Release);
235            let progress = context.progress.load(Ordering::Relaxed);
236            self.completed = true;
237
238            return core::task::Poll::Ready(progress);
239        }
240        core::task::Poll::Pending
241    }
242}
243
244impl Drop for TxFuture<'_, '_> {
245    fn drop(&mut self) {
246        // On cancellation, clear the stale buffer pointer so a spurious or future interrupt
247        // for this waker slot can never dereference it. `self.completed` (set inside `poll`'s
248        // `Ready` arm) is what actually distinguishes cancellation from normal completion here,
249        // since `TX_DONE` itself is already swapped back to `false` by the time a completed
250        // future is dropped.
251        if !self.completed {
252            let context_ref = &TX_CONTEXTS[self.waker_idx];
253            context_ref.progress.store(0, Ordering::Relaxed);
254            context_ref
255                .raw_data
256                .store(core::ptr::null_mut(), Ordering::Release);
257            // We can not disable interrupts, might be active for RX as well.
258            self.tx.tx.reset_fifo();
259        }
260    }
261}
262
263/// Asynchronous TX driver.
264///
265/// Relies on [on_interrupt_tx] being called with this driver's [TxToken] (see [Self::token])
266/// from the UART interrupt handler: without it, futures returned by [Self::write] never make
267/// progress past the initial FIFO fill and never complete.
268pub struct TxAsync {
269    pub(crate) tx: Tx,
270    token: TxToken,
271}
272
273impl TxAsync {
274    /// Create a new asynchronous TX structure.
275    ///
276    /// # Safety
277    ///
278    /// The user MUST ensure that the `Drop` method of all futures generated with this driver
279    /// is called on transfer cancellation. By default, this does not require any special handling.
280    /// This case was considered exotic enough to not justify an `unsafe` API.
281    pub fn new(tx: Tx, waker_idx: usize) -> Result<Self, InvalidWakerIndex> {
282        if waker_idx >= NUM_WAKERS {
283            return Err(InvalidWakerIndex(waker_idx));
284        }
285        let token = TxToken {
286            // Safety: only converted to a primitive address.
287            base_addr: unsafe { tx.regs.ptr() } as usize,
288            waker_idx,
289        };
290        Ok(Self { tx, token })
291    }
292
293    /// The token identifying this driver's UART instance and waker slot, fixed for its whole
294    /// lifetime. Retrieve it once, right after construction, to hand to [on_interrupt_tx] in
295    /// your interrupt handler.
296    ///
297    /// Since the token needs to reach a separate interrupt context, a crate like `once_cell` can
298    /// be used to share it safely.
299    #[inline]
300    pub fn token(&self) -> TxToken {
301        self.token
302    }
303
304    /// Write a buffer asynchronously.
305    ///
306    /// This implementation is not side effect free, and a started future might have already
307    /// written part of the passed buffer.
308    pub fn write<'buf>(&mut self, buf: &'buf [u8]) -> TxFuture<'_, 'buf> {
309        TxFuture::new(self, self.token.waker_idx, buf).expect("waker index unexpectedly invalid")
310    }
311
312    /// Release the owned TX structure.
313    pub fn release(self) -> Tx {
314        self.tx
315    }
316}
317
318impl embedded_io::ErrorType for TxAsync {
319    type Error = Infallible;
320}
321
322impl embedded_io_async::Write for TxAsync {
323    /// Write a buffer asynchronously.
324    ///
325    /// This implementation is not side effect free, and a started future might have already
326    /// written part of the passed buffer.
327    async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
328        Ok(self.write(buf).await)
329    }
330
331    /// This implementation does not do anything.
332    async fn flush(&mut self) -> Result<(), Self::Error> {
333        Ok(())
334    }
335}