diff --git a/va108xx/examples/embassy/src/bin/async-uart-tx.rs b/va108xx/examples/embassy/src/bin/async-uart-tx.rs index 44db1bd..3ef103f 100644 --- a/va108xx/examples/embassy/src/bin/async-uart-tx.rs +++ b/va108xx/examples/embassy/src/bin/async-uart-tx.rs @@ -70,10 +70,13 @@ async fn main(_spawner: Spawner) { led0.toggle(); led1.toggle(); led2.toggle(); - async_tx - .write_all(STR_LIST[idx].as_bytes()) - .await - .expect("writing failed"); + // Safety: We are sending static lifetime slices, and not cancelling the futures. + unsafe { + async_tx + .write_all(STR_LIST[idx].as_bytes()) + .await + .expect("writing failed"); + } idx += 1; if idx == STR_LIST.len() { idx = 0; diff --git a/va108xx/flashloader/src/main.rs b/va108xx/flashloader/src/main.rs index 1ccf03d..60d7686 100644 --- a/va108xx/flashloader/src/main.rs +++ b/va108xx/flashloader/src/main.rs @@ -284,7 +284,8 @@ mod app { let mut buf: [u8; 256] = [0; 256]; loop { let read_len = cx.local.tm_rx.read(&mut buf).await; - if let Err(e) = cx.local.uart_tx.write_all(&buf[0..read_len]).await { + // Safety: The buffer outlives the UART TX structure. + if let Err(e) = unsafe { cx.local.uart_tx.write_all(&buf[0..read_len]).await } { defmt::warn!("UART TX overrun error: {}", e); } } diff --git a/va108xx/va108xx-hal/CHANGELOG.md b/va108xx/va108xx-hal/CHANGELOG.md index 32913e1..d9eefba 100644 --- a/va108xx/va108xx-hal/CHANGELOG.md +++ b/va108xx/va108xx-hal/CHANGELOG.md @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +### Changed + +- Async TX UART functions are explicitely marked `unsafe`. +- Async TX UART `write` now returns a `TxFuture` +- Empty async TX write resolves to `Poll::Ready(0)` immediately + ## [v0.13.1] 2026-05-19 - Docs.rs feature fix diff --git a/va416xx/examples/embassy/src/bin/async-uart-tx.rs b/va416xx/examples/embassy/src/bin/async-uart-tx.rs index 5f6befd..62a01ac 100644 --- a/va416xx/examples/embassy/src/bin/async-uart-tx.rs +++ b/va416xx/examples/embassy/src/bin/async-uart-tx.rs @@ -74,10 +74,13 @@ async fn main(_spawner: Spawner) { loop { defmt::println!("Current time: {}", Instant::now().as_secs()); led.toggle(); - async_tx - .write_all(STR_LIST[idx].as_bytes()) - .await - .expect("writing failed"); + // Safety: We are sending static lifetime slices, and not cancelling the futures. + unsafe { + async_tx + .write_all(STR_LIST[idx].as_bytes()) + .await + .expect("writing failed"); + } idx += 1; if idx == STR_LIST.len() { idx = 0; diff --git a/va416xx/va416xx-hal/CHANGELOG.md b/va416xx/va416xx-hal/CHANGELOG.md index 7db5166..1a43001 100644 --- a/va416xx/va416xx-hal/CHANGELOG.md +++ b/va416xx/va416xx-hal/CHANGELOG.md @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [unreleased] +### Changed + +- Async TX UART functions are explicitely marked `unsafe`. +- Async TX UART `write` now returns a `TxFuture` +- Empty async TX write resolves to `Poll::Ready(0)` immediately + # [v0.6.0] 2025-09-03 - Use `vorago-shared-hal` dependency to provide shared peripherals. diff --git a/vorago-shared-hal/CHANGELOG.md b/vorago-shared-hal/CHANGELOG.md index 97efcbc..aacdb4a 100644 --- a/vorago-shared-hal/CHANGELOG.md +++ b/vorago-shared-hal/CHANGELOG.md @@ -8,9 +8,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +### Changed + +- Async TX UART functions are explicitely marked `unsafe`. +- Async TX UART `write` now returns a `TxFuture` +- Empty async TX write resolves to `Poll::Ready(0)` immediately + ## [v0.4.0] 2026-05-19 -## Changed +### Changed - Naming improvements for UART register module - Improved UART Async TX module. Only enable TX below threshold interrupts if the FIFO diff --git a/vorago-shared-hal/src/uart/mod.rs b/vorago-shared-hal/src/uart/mod.rs index 56cda9e..e99abf8 100644 --- a/vorago-shared-hal/src/uart/mod.rs +++ b/vorago-shared-hal/src/uart/mod.rs @@ -1138,6 +1138,14 @@ pub struct Tx { regs: regs::MmioUart<'static>, } +impl core::fmt::Debug for Tx { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Tx") + .field("id", &self.id) + .finish_non_exhaustive() + } +} + impl Tx { /// Retrieve a TX pin without expecting an explicit UART structure /// diff --git a/vorago-shared-hal/src/uart/tx_async.rs b/vorago-shared-hal/src/uart/tx_async.rs index 4512195..8d96ae4 100644 --- a/vorago-shared-hal/src/uart/tx_async.rs +++ b/vorago-shared-hal/src/uart/tx_async.rs @@ -112,8 +112,10 @@ impl TxContext { } } +#[derive(Debug)] pub struct TxFuture { id: Bank, + empty_buffer: bool, } impl TxFuture { @@ -122,6 +124,14 @@ impl TxFuture { /// This function stores the raw pointer of the passed data slice. The user MUST ensure /// that the slice outlives the data structure. pub unsafe fn new(tx: &mut Tx, data: &[u8]) -> Self { + if data.is_empty() { + // We can just return a dummy future which is immediately ready, no need to set up + // interrupts etc. + return Self { + id: tx.id, + empty_buffer: true, + }; + } TX_DONE[tx.id as usize].store(false, core::sync::atomic::Ordering::Relaxed); tx.disable_interrupts(); tx.disable(); @@ -147,7 +157,10 @@ impl TxFuture { ); tx.enable(); }); - Self { id: tx.id } + Self { + id: tx.id, + empty_buffer: false, + } } } @@ -158,6 +171,9 @@ impl Future for TxFuture { self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, ) -> core::task::Poll { + if self.empty_buffer { + return core::task::Poll::Ready(Ok(0)); + } UART_TX_WAKERS[self.id as usize].register(cx.waker()); if TX_DONE[self.id as usize].swap(false, core::sync::atomic::Ordering::Relaxed) { let progress = critical_section::with(|cs| { @@ -169,16 +185,23 @@ impl Future for TxFuture { } } +/// Safety note: +/// +/// It is imperative that this `Drop` method is executed to avoid undefined behaviour on +/// transfer. Do *NOT* use `core::mem::forget` on the `TxFuture`. impl Drop for TxFuture { fn drop(&mut self) { let mut reg_block = unsafe { self.id.steal_regs() }; - if !TX_DONE[self.id as usize].load(core::sync::atomic::Ordering::Relaxed) { + if !TX_DONE[self.id as usize].load(core::sync::atomic::Ordering::Relaxed) + && !self.empty_buffer + { disable_tx_interrupts(&mut reg_block); disable_tx(&mut reg_block); } } } +#[derive(Debug)] pub struct TxAsync(Tx); impl TxAsync { @@ -195,9 +218,13 @@ impl TxAsync { /// /// This implementation is not side effect free, and a started future might have already /// written part of the passed buffer. - pub async fn write(&mut self, buf: &[u8]) -> Result { - let fut = unsafe { TxFuture::new(&mut self.0, buf) }; - fut.await + /// + /// # Safety + /// + /// This function stores the raw pointer of the passed data slice. The user MUST ensure + /// that the slice outlives the data structure. + pub unsafe fn write(&mut self, buf: &[u8]) -> TxFuture { + unsafe { TxFuture::new(&mut self.0, buf) } } /// Write an entire buffer into this writer. @@ -207,7 +234,12 @@ impl TxAsync { /// /// This function is not side-effect-free on cancel (AKA "cancel-safe"), i.e. if you cancel (drop) a returned /// future that hasn't completed yet, some bytes might have already been written. - pub async fn write_all(&mut self, buf: &[u8]) -> Result<(), TxOverrunError> { + /// + /// # Safety + /// + /// This function stores the raw pointer of the passed data slice. The user MUST ensure + /// that the slice outlives the data structure. + pub async unsafe fn write_all(&mut self, buf: &[u8]) -> Result<(), TxOverrunError> { let fut = ::write_all(self, buf); fut.await } @@ -242,8 +274,16 @@ impl Write for TxAsync { /// /// This implementation is not side effect free, and a started future might have already /// written part of the passed buffer. + /// + /// # Safety + /// + /// This function is not `unsafe` due to the trait definition. + /// This function stores the raw pointer of the passed data slice. The user MUST ensure + /// that the slice outlives the data structure. async fn write(&mut self, buf: &[u8]) -> Result { - self.write(buf).await + // Safety: We documented the safety contract. Not much else we can do here as we are bound + // by the trait definition. + unsafe { self.write(buf).await } } async fn flush(&mut self) -> Result<(), Self::Error> {