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}