From 9fe02b819c3105218008891e33bbfcab23050969 Mon Sep 17 00:00:00 2001 From: Szybet <53944559+Szybet@users.noreply.github.com> Date: Fri, 29 Aug 2025 10:05:17 +0200 Subject: [PATCH] UART using DMA (via UHCI) (#3871) * working rx * very sketchy working tx * uart uhci qa example * move it * cleanout * read working great, write outputs the whole buffer * tx working but rx can freeze * fix freezes * dont use vec * cleaning * async weird and not working * async working? * into async * Apply suggested changes * it compiles * it also works with 'd * split into seperate structs * Add uart config configuration after uhci consumed it * working Uhci normal * again aaa sorry * Apply suggested changes (That I knew how to implement) * Add proper publicity, reimplement wrappers for internal functions using a macro, repair examples * Moved to config, uhci normal example doesn't echo * still not working * hacky works normal * apply suggested changes * Initial docs * fix messing up rx tx, still doesn't work * Workaround, working but well * change back to not pub (Maybe redo to public later, for now just to not mess with the workaround) * now its working * forgot to push * Revert 2 commits, implement drop to showcase problem * apply some suggested changes * apply more suggested fixes (async/blocking) * Everything works * clearing out warnings * handle errors * merge from upstream, make it compile (not sure) * Apply almost all suggestions * re add wait for done * Move errors out of wait_for_done, formating, changelog * Apply suggested changes * apply lint suggestions * docs * No longer public * hil tests, rx tx transfer * implement split * forgot to format * apply suggested changes * Add top level module doc * cicd now working? * format, cicd maybe now * apply suggestion * format fix * modify to write --- esp-hal/CHANGELOG.md | 1 + esp-hal/src/{uart.rs => uart/mod.rs} | 5 + esp-hal/src/uart/uhci.rs | 708 +++++++++++++++++++++++++++ hil-test/Cargo.toml | 5 + hil-test/tests/uart_uhci.rs | 156 ++++++ 5 files changed, 875 insertions(+) rename esp-hal/src/{uart.rs => uart/mod.rs} (99%) create mode 100644 esp-hal/src/uart/uhci.rs create mode 100644 hil-test/tests/uart_uhci.rs diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 10979ac78..1af35f6ff 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `aes::{AesBackend, AesContext, dma::AesDmaBackend}`: Work-queue based AES driver (#3880, #3897) - `aes::cipher_modes`, `aes::CipherState` for constructing `AesContext`s (#3895) - `aes::dma::DmaCipherState` so that `AesDma` can properly support cipher modes that require state (IV, nonce, etc.) (#3897) +- `uart::Uhci`: for UART with DMA using the UHCI peripheral (#3871) - Align `I8080` driver pin configurations with latest guidelines (#3997) - Expose cache line configuration (#3946) - ESP32: Expose `psram_vaddr_mode` via `PsramConfig` (#3990) diff --git a/esp-hal/src/uart.rs b/esp-hal/src/uart/mod.rs similarity index 99% rename from esp-hal/src/uart.rs rename to esp-hal/src/uart/mod.rs index eeed226d6..a6bd2bbb6 100644 --- a/esp-hal/src/uart.rs +++ b/esp-hal/src/uart/mod.rs @@ -41,6 +41,11 @@ //! [embedded-hal-async]: embedded_hal_async //! [embedded-io-async]: embedded_io_async +/// UHCI wrapper around UART (Only esp32c6) +#[cfg(esp32c6)] +#[cfg(feature = "unstable")] +pub mod uhci; + use core::{marker::PhantomData, sync::atomic::Ordering, task::Poll}; #[cfg(feature = "unstable")] diff --git a/esp-hal/src/uart/uhci.rs b/esp-hal/src/uart/uhci.rs new file mode 100644 index 000000000..ff3b50311 --- /dev/null +++ b/esp-hal/src/uart/uhci.rs @@ -0,0 +1,708 @@ +#![cfg_attr(docsrs, procmacros::doc_replace)] +//! ## Usage +//! ```rust, no_run +//! #![no_std] +//! #![no_main] +//! +//! #[panic_handler] +//! fn panic(_: &core::panic::PanicInfo) -> ! { +//! loop {} +//! } +//! +//! use esp_hal::{ +//! clock::CpuClock, +//! dma::{DmaRxBuf, DmaTxBuf}, +//! dma_buffers, +//! main, +//! rom::software_reset, +//! uart, +//! uart::{RxConfig, Uart, uhci, uhci::Uhci}, +//! }; +//! +//! #[main] +//! fn main() -> ! { +//! let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); +//! let peripherals = esp_hal::init(config); +//! let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); +//! let peripherals = esp_hal::init(config); +//! +//! let config = uart::Config::default() +//! .with_rx(RxConfig::default().with_fifo_full_threshold(64)) +//! .with_baudrate(115200); +//! +//! let uart = Uart::new(peripherals.UART1, config) +//! .unwrap() +//! .with_tx(peripherals.GPIO2) +//! .with_rx(peripherals.GPIO3); +//! +//! let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(4092); +//! let dma_rx = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap(); +//! let mut dma_tx = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap(); +//! +//! let mut uhci = Uhci::new(uart, peripherals.UHCI0, peripherals.DMA_CH0); +//! uhci.apply_config(&uhci::Config::default().with_chunk_limit(dma_rx.len() as u16)) +//! .unwrap(); +//! +//! let config = uart::Config::default() +//! .with_rx(RxConfig::default().with_fifo_full_threshold(64)) +//! .with_baudrate(9600); +//! uhci.set_uart_config(&config).unwrap(); +//! +//! let (uhci_rx, uhci_tx) = uhci.split(); +//! // Waiting for message +//! let transfer = uhci_rx +//! .read(dma_rx) +//! .unwrap_or_else(|x| panic!("Something went horribly wrong: {:?}", x.0)); +//! let (err, _uhci_rx, dma_rx) = transfer.wait(); +//! err.unwrap(); +//! +//! let received = dma_rx.number_of_received_bytes(); +//! // println!("Received dma bytes: {}", received); +//! +//! let rec_slice = &dma_rx.as_slice()[0..received]; +//! if received > 0 { +//! match core::str::from_utf8(&rec_slice) { +//! Ok(x) => { +//! // println!("Received DMA message: \"{}\"", x); +//! dma_tx.as_mut_slice()[0..received].copy_from_slice(&rec_slice); +//! dma_tx.set_length(received); +//! let transfer = uhci_tx +//! .write(dma_tx) +//! .unwrap_or_else(|x| panic!("Something went horribly wrong: {:?}", x.0)); +//! let (err, _uhci, _dma_tx) = transfer.wait(); +//! err.unwrap(); +//! } +//! Err(x) => panic!("Error string: {}", x), +//! } +//! } +//! software_reset() +//! } +//! ``` + +use core::{ + mem::ManuallyDrop, + ops::{Deref, DerefMut}, +}; + +use embassy_embedded_hal::SetConfig; + +use crate::{ + Async, + Blocking, + DriverMode, + dma::{ + AnyGdmaRxChannel, + AnyGdmaTxChannel, + Channel, + ChannelRx, + ChannelTx, + DmaChannelFor, + DmaEligible, + DmaError, + DmaRxBuffer, + DmaTxBuffer, + PeripheralDmaChannel, + asynch::{DmaRxFuture, DmaTxFuture}, + }, + pac::uhci0, + peripherals, + uart::{self, TxError, Uart, UartRx, UartTx, uhci::Error::AboveReadLimit}, +}; + +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +/// Uhci specific errors +pub enum Error { + /// set_chunk_limit() argument is above what's possible by the hardware. It cannot exceed 4095 + /// (12 bits), above this value it will simply also split the readings + AboveReadLimit, + /// DMA originating error + Dma(DmaError), + /// UART Tx originating error + Tx(TxError), +} + +impl From for Error { + fn from(value: DmaError) -> Self { + Error::Dma(value) + } +} + +impl From for Error { + fn from(value: TxError) -> Self { + Error::Tx(value) + } +} + +crate::any_peripheral! { + pub peripheral AnyUhci<'d> { + Uhci0(crate::peripherals::UHCI0<'d>), + } +} + +impl<'d> DmaEligible for AnyUhci<'d> { + #[cfg(gdma)] + type Dma = crate::dma::AnyGdmaChannel<'d>; + + fn dma_peripheral(&self) -> crate::dma::DmaPeripheral { + match &self.0 { + any::Inner::Uhci0(_) => crate::dma::DmaPeripheral::Uhci0, + } + } +} + +impl AnyUhci<'_> { + /// Opens the enum into the peripheral below + fn give_uhci(&self) -> &peripherals::UHCI0<'_> { + match &self.0 { + any::Inner::Uhci0(x) => x, + } + } +} + +/// A configuration error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ConfigError { + /// chunk_limit is above 4095, this is not allowed (hardware limit) + AboveReadLimit, +} + +/// UHCI Configuration +#[derive(Debug, Clone, Copy, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Config { + /// If this is set to true UHCI will end the payload receiving process when UART has been in + /// idle state. + idle_eof: bool, + /// If this is set to true UHCI decoder receiving payload data ends when the receiving + /// byte count has reached the specified value (in len_eof). + /// If this is set to false UHCI decoder receiving payload data is end when 0xc0 is received. + len_eof: bool, + /// The limit of how much to read in a single read call. It cannot be higher than the dma + /// buffer size, otherwise uart/dma/uhci will freeze. It cannot exceed 4095 (12 bits), above + /// this value it will simply also split the readings + chunk_limit: u16, +} + +impl Default for Config { + fn default() -> Config { + Config { + idle_eof: true, + len_eof: true, + // This is the default in the register at boot, still should be changed! + chunk_limit: 128, + } + } +} + +impl core::error::Error for ConfigError {} + +impl core::fmt::Display for ConfigError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ConfigError::AboveReadLimit => { + write!( + f, + "The requested read limit is not possible. The max is 4095 (12 bits)" + ) + } + } + } +} + +impl embassy_embedded_hal::SetConfig for Uhci<'_, Dm> +where + Dm: DriverMode, +{ + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.apply_config(config) + } +} + +/// UHCI (To use with UART over DMA) +pub struct Uhci<'d, Dm> +where + Dm: DriverMode, +{ + uart: Uart<'d, Dm>, + uhci: AnyUhci<'static>, + channel: Channel>>, +} + +impl<'d, Dm> Uhci<'d, Dm> +where + Dm: DriverMode, +{ + fn init(&self) { + self.clean_turn_on(); + self.reset(); + self.conf_uart(); + } + + fn clean_turn_on(&self) { + // General conf registers + let reg: &uhci0::RegisterBlock = self.uhci.give_uhci().register_block(); + reg.conf0().modify(|_, w| w.clk_en().set_bit()); + reg.conf0().write(|w| { + unsafe { w.bits(0) }; + w.clk_en().set_bit() + }); + reg.conf1().modify(|_, w| unsafe { w.bits(0) }); + + // For TX + reg.escape_conf().modify(|_, w| unsafe { w.bits(0) }); + } + + fn reset(&self) { + let reg: &uhci0::RegisterBlock = self.uhci.give_uhci().register_block(); + reg.conf0().modify(|_, w| w.rx_rst().set_bit()); + reg.conf0().modify(|_, w| w.rx_rst().clear_bit()); + + reg.conf0().modify(|_, w| w.tx_rst().set_bit()); + reg.conf0().modify(|_, w| w.tx_rst().clear_bit()); + } + + fn conf_uart(&self) { + let reg: &uhci0::RegisterBlock = self.uhci.give_uhci().register_block(); + + // Idk if there is a better way to check it, but it works + match &self.uart.tx.uart.0 { + super::any::Inner::Uart0(_) => { + info!("Uhci will use uart0"); + reg.conf0().modify(|_, w| w.uart0_ce().set_bit()); + } + super::any::Inner::Uart1(_) => { + info!("Uhci will use uart1"); + reg.conf0().modify(|_, w| w.uart1_ce().set_bit()); + } + } + } + + #[allow(dead_code)] + fn set_chunk_limit(&self, limit: u16) -> Result<(), Error> { + let reg: &uhci0::RegisterBlock = self.uhci.give_uhci().register_block(); + // let val = reg.pkt_thres().read().pkt_thrs().bits(); + // info!("Read limit value: {} to set: {}", val, limit); + + // limit is 12 bits + // Above this value, it will probably split the messages, anyway, the point is below (the + // dma buffer length) it it will not freeze itself + if limit > 4095 { + return Err(AboveReadLimit); + } + + reg.pkt_thres().write(|w| unsafe { w.bits(limit as u32) }); + Ok(()) + } + + /// Sets the config the the consumed UART + pub fn set_uart_config(&mut self, uart_config: &uart::Config) -> Result<(), uart::ConfigError> { + self.uart.set_config(uart_config) + } + + /// Sets the config to the UHCI peripheral + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + let reg: &uhci0::RegisterBlock = self.uhci.give_uhci().register_block(); + + reg.conf0() + .modify(|_, w| w.uart_idle_eof_en().bit(config.idle_eof)); + + reg.conf0() + .modify(|_, w| w.len_eof_en().bit(config.len_eof)); + + if self.set_chunk_limit(config.chunk_limit).is_err() { + return Err(ConfigError::AboveReadLimit); + } + + Ok(()) + } + + /// Split the Uhci into UhciRx and UhciTx + pub fn split(self) -> (UhciRx<'d, Dm>, UhciTx<'d, Dm>) { + let (uart_rx, uart_tx) = self.uart.split(); + ( + UhciRx { + uhci: unsafe { self.uhci.clone_unchecked() }, + uart_rx, + channel_rx: self.channel.rx, + }, + UhciTx { + uhci: self.uhci, + uart_tx, + channel_tx: self.channel.tx, + }, + ) + } +} + +impl<'d> Uhci<'d, Blocking> { + /// Creates a new instance of UHCI + pub fn new( + uart: Uart<'d, Blocking>, + uhci: peripherals::UHCI0<'static>, + channel: impl DmaChannelFor>, + ) -> Self { + let channel = Channel::new(channel.degrade()); + channel.runtime_ensure_compatible(&uhci); + + let uhci = Uhci { + uart, + uhci: uhci.into(), + channel, + }; + + uhci.init(); + uhci + } + + /// Create a new instance in [crate::Async] mode. + pub fn into_async(self) -> Uhci<'d, Async> { + Uhci { + uart: self.uart.into_async(), + uhci: self.uhci, + channel: self.channel.into_async(), + } + } +} + +impl<'d> Uhci<'d, Async> { + /// Create a new instance in [crate::Blocking] mode. + pub fn into_blocking(self) -> Uhci<'d, Blocking> { + Uhci { + uart: self.uart.into_blocking(), + uhci: self.uhci, + channel: self.channel.into_blocking(), + } + } +} + +/// Splitted Uhci structs, Tx part for sending data +pub struct UhciTx<'d, Dm> +where + Dm: DriverMode, +{ + uhci: AnyUhci<'static>, + uart_tx: UartTx<'d, Dm>, + channel_tx: ChannelTx>, +} + +impl<'d, Dm> UhciTx<'d, Dm> +where + Dm: DriverMode, +{ + /// Starts the write DMA transfer and returns the instance of UhciDmaTxTransfer + pub fn write( + mut self, + mut tx_buffer: Buf, + ) -> Result, (Error, Self, Buf)> { + let res = unsafe { + self.channel_tx + .prepare_transfer(self.uhci.dma_peripheral(), &mut tx_buffer) + }; + if let Err(err) = res { + return Err((err.into(), self, tx_buffer)); + } + + let res = self.channel_tx.start_transfer(); + if let Err(err) = res { + return Err((err.into(), self, tx_buffer)); + } + + Ok(UhciDmaTxTransfer::new(self, tx_buffer)) + } +} + +/// Splitted Uhci structs, Rx part for receiving data +pub struct UhciRx<'d, Dm> +where + Dm: DriverMode, +{ + uhci: AnyUhci<'static>, + #[allow(dead_code)] + uart_rx: UartRx<'d, Dm>, + channel_rx: ChannelRx>, +} + +impl<'d, Dm> UhciRx<'d, Dm> +where + Dm: DriverMode, +{ + /// Starts the read DMA transfer and returns the instance of UhciDmaRxTransfer + pub fn read( + mut self, + mut rx_buffer: Buf, + ) -> Result, (Error, Self, Buf)> { + { + let res = unsafe { + self.channel_rx + .prepare_transfer(self.uhci.dma_peripheral(), &mut rx_buffer) + }; + if let Err(err) = res { + return Err((err.into(), self, rx_buffer)); + } + + let res = self.channel_rx.start_transfer(); + if let Err(err) = res { + return Err((err.into(), self, rx_buffer)); + } + + Ok(UhciDmaRxTransfer::new(self, rx_buffer)) + } + } +} + +/// A structure representing a DMA transfer for UHCI/UART. +/// +/// This structure holds references to the UHCI instance, DMA buffers, and +/// transfer status. +pub struct UhciDmaTxTransfer<'d, Dm, Buf> +where + Dm: DriverMode, + Buf: DmaTxBuffer, +{ + uhci: ManuallyDrop>, + dma_buf: ManuallyDrop, + done: bool, + saved_err: Result<(), Error>, +} + +impl<'d, Buf: DmaTxBuffer, Dm: DriverMode> UhciDmaTxTransfer<'d, Dm, Buf> { + fn new(uhci: UhciTx<'d, Dm>, dma_buf: Buf) -> Self { + Self { + uhci: ManuallyDrop::new(uhci), + dma_buf: ManuallyDrop::new(dma_buf.into_view()), + done: false, + saved_err: Ok(()), + } + } + + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + self.uhci.channel_tx.is_done() + } + + /// Cancels the DMA transfer. + pub fn cancel(mut self) -> (UhciTx<'d, Dm>, Buf::Final) { + self.uhci.channel_tx.stop_transfer(); + + let retval = unsafe { + ( + ManuallyDrop::take(&mut self.uhci), + Buf::from_view(ManuallyDrop::take(&mut self.dma_buf)), + ) + }; + core::mem::forget(self); + retval + } + + /// Waits for the DMA transfer to complete. + /// + /// This method blocks until the transfer is finished and returns the + /// `Uhci` instance and the associated buffer. + pub fn wait(mut self) -> (Result<(), Error>, UhciTx<'d, Dm>, Buf::Final) { + if let Err(err) = self.saved_err { + return ( + Err(err), + unsafe { ManuallyDrop::take(&mut self.uhci) }, + unsafe { Buf::from_view(ManuallyDrop::take(&mut self.dma_buf)) }, + ); + } + + if !self.done { + let res = self.uhci.uart_tx.flush(); + if let Err(err) = res { + return ( + Err(err.into()), + unsafe { ManuallyDrop::take(&mut self.uhci) }, + unsafe { Buf::from_view(ManuallyDrop::take(&mut self.dma_buf)) }, + ); + } + + while !self.is_done() {} + } + + self.uhci.channel_tx.stop_transfer(); + let retval = unsafe { + ( + Result::<(), Error>::Ok(()), + ManuallyDrop::take(&mut self.uhci), + Buf::from_view(ManuallyDrop::take(&mut self.dma_buf)), + ) + }; + core::mem::forget(self); + retval + } +} + +impl<'d, Buf: DmaTxBuffer> UhciDmaTxTransfer<'d, Async, Buf> { + /// Waits for the DMA transfer to complete, but async. After that, you still need to wait() + pub async fn wait_for_done(&mut self) { + // Workaround for an issue when it doesn't actually wait for the transfer to complete. I'm + // lost at this point, this is the only thing that worked + let res = self.uhci.uart_tx.flush_async().await; + if let Err(err) = res { + self.saved_err = Err(err.into()); + return; + } + + let res = DmaTxFuture::new(&mut self.uhci.channel_tx).await; + if let Err(err) = res { + self.saved_err = Err(err.into()); + return; + } + + self.done = true; + } +} + +impl Deref for UhciDmaTxTransfer<'_, Dm, Buf> { + type Target = Buf::View; + + fn deref(&self) -> &Self::Target { + &self.dma_buf + } +} + +impl DerefMut for UhciDmaTxTransfer<'_, Dm, Buf> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.dma_buf + } +} + +impl Drop for UhciDmaTxTransfer<'_, Dm, Buf> +where + Dm: DriverMode, + Buf: DmaTxBuffer, +{ + fn drop(&mut self) { + self.uhci.channel_tx.stop_transfer(); + + unsafe { + ManuallyDrop::drop(&mut self.uhci); + drop(Buf::from_view(ManuallyDrop::take(&mut self.dma_buf))); + } + } +} + +/// A structure representing a DMA transfer for UHCI/UART. +/// +/// This structure holds references to the UHCI instance, DMA buffers, and +/// transfer status. +pub struct UhciDmaRxTransfer<'d, Dm, Buf> +where + Dm: DriverMode, + Buf: DmaRxBuffer, +{ + uhci: ManuallyDrop>, + dma_buf: ManuallyDrop, + done: bool, + saved_err: Result<(), Error>, +} + +impl<'d, Buf: DmaRxBuffer, Dm: DriverMode> UhciDmaRxTransfer<'d, Dm, Buf> { + fn new(uhci: UhciRx<'d, Dm>, dma_buf: Buf) -> Self { + Self { + uhci: ManuallyDrop::new(uhci), + dma_buf: ManuallyDrop::new(dma_buf.into_view()), + done: false, + saved_err: Ok(()), + } + } + + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + self.uhci.channel_rx.is_done() + } + + /// Cancels the DMA transfer. + pub fn cancel(mut self) -> (UhciRx<'d, Dm>, Buf::Final) { + self.uhci.channel_rx.stop_transfer(); + + let retval = unsafe { + ( + ManuallyDrop::take(&mut self.uhci), + Buf::from_view(ManuallyDrop::take(&mut self.dma_buf)), + ) + }; + core::mem::forget(self); + retval + } + + /// Waits for the DMA transfer to complete. + /// + /// This method blocks until the transfer is finished and returns the + /// `Uhci` instance and the associated buffer. + pub fn wait(mut self) -> (Result<(), Error>, UhciRx<'d, Dm>, Buf::Final) { + if let Err(err) = self.saved_err { + return ( + Err(err), + unsafe { ManuallyDrop::take(&mut self.uhci) }, + unsafe { Buf::from_view(ManuallyDrop::take(&mut self.dma_buf)) }, + ); + } + + if !self.done { + while !self.is_done() {} + } + self.uhci.channel_rx.stop_transfer(); + + let retval = unsafe { + ( + Result::<(), Error>::Ok(()), + ManuallyDrop::take(&mut self.uhci), + Buf::from_view(ManuallyDrop::take(&mut self.dma_buf)), + ) + }; + core::mem::forget(self); + retval + } +} + +impl<'d, Buf: DmaRxBuffer> UhciDmaRxTransfer<'d, Async, Buf> { + /// Waits for the DMA transfer to complete, but async. After that, you still need to wait() + pub async fn wait_for_done(&mut self) { + let res = DmaRxFuture::new(&mut self.uhci.channel_rx).await; + if let Err(err) = res { + self.saved_err = Err(err.into()); + return; + } + + self.done = true; + } +} + +impl Deref for UhciDmaRxTransfer<'_, Dm, Buf> { + type Target = Buf::View; + + fn deref(&self) -> &Self::Target { + &self.dma_buf + } +} + +impl DerefMut for UhciDmaRxTransfer<'_, Dm, Buf> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.dma_buf + } +} + +impl Drop for UhciDmaRxTransfer<'_, Dm, Buf> +where + Dm: DriverMode, + Buf: DmaRxBuffer, +{ + fn drop(&mut self) { + self.uhci.channel_rx.stop_transfer(); + + unsafe { + ManuallyDrop::drop(&mut self.uhci); + drop(Buf::from_view(ManuallyDrop::take(&mut self.dma_buf))); + } + } +} diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index d88c1a3da..905ae537e 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -173,6 +173,11 @@ name = "uart_async" harness = false required-features = ["embassy"] +[[test]] +name = "uart_uhci" +harness = false +required-features = ["embassy"] + [[test]] name = "uart_regression" harness = false diff --git a/hil-test/tests/uart_uhci.rs b/hil-test/tests/uart_uhci.rs new file mode 100644 index 000000000..82d4731fc --- /dev/null +++ b/hil-test/tests/uart_uhci.rs @@ -0,0 +1,156 @@ +//! UART UHCI test, async + +//% CHIPS: esp32c6 +//% FEATURES: unstable embassy + +#![no_std] +#![no_main] + +use esp_hal::{ + Blocking, + dma::{DmaRxBuf, DmaTxBuf}, + dma_buffers, + uart::{self, Uart, uhci::Uhci}, +}; + +struct Context { + uhci: Uhci<'static, Blocking>, + dma_rx: DmaRxBuf, + dma_tx: DmaTxBuf, +} + +#[cfg(test)] +#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())] +mod tests { + use super::*; + + #[init] + async fn init() -> Context { + let peripherals = esp_hal::init(esp_hal::Config::default()); + + let (rx, tx) = hil_test::common_test_pins!(peripherals); + + let uart = Uart::new(peripherals.UART0, uart::Config::default()) + .unwrap() + .with_tx(tx) + .with_rx(rx); + + let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(4092); + let dma_rx = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap(); + let dma_tx = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap(); + + let mut uhci = Uhci::new(uart, peripherals.UHCI0, peripherals.DMA_CH0); + uhci.apply_config(&uart::uhci::Config::default().with_chunk_limit(dma_rx.len() as u16)) + .unwrap(); + + Context { + uhci, + dma_rx, + dma_tx, + } + } + + #[test] + fn test_send_receive(mut ctx: Context) { + const SEND: &[u8] = b"Hello ESP32"; + ctx.dma_tx.as_mut_slice()[0..SEND.len()].copy_from_slice(&SEND); + ctx.dma_tx.set_length(SEND.len()); + + let (uhci_rx, uhci_tx) = ctx.uhci.split(); + let transfer_rx = uhci_rx + .read(ctx.dma_rx) + .unwrap_or_else(|x| panic!("Something went horribly wrong: {:?}", x.0)); + let transfer_tx = uhci_tx + .write(ctx.dma_tx) + .unwrap_or_else(|x| panic!("Something went horribly wrong: {:?}", x.0)); + let (res, _uhci_tx, _dma_tx) = transfer_tx.wait(); + res.unwrap(); + let (res, _uhci_rx, dma_rx) = transfer_rx.wait(); + res.unwrap(); + + assert_eq!( + &dma_rx.as_slice()[0..dma_rx.number_of_received_bytes()], + SEND + ); + } + + #[test] + fn test_long_strings(mut ctx: Context) { + const LONG_TEST_STRING: &str = "Loremipsumdolorsitamet,consecteturadipiscingelit.Suspendissemetusnisl,pretiumsedeuismodeget,bibendumeusem.Donecaccumsanrisusnibh,etefficiturnisivehiculatempus.Etiamegestasenimatduieleifendmaximus.Nuncinsemperest.Etiamvelodioultrices,interdumeratsed,dignissimmetus.Phasellusexleo,eleifendquisexid,laciniavenenatisneque.Sednuncdiam,molestieveltinciduntnec,ornareetnisi.Maecenasetmolestietortor.Nullaeupulvinarquam.Aeneanmolestieliberoquistortorviverralobortis.Praesentlaoreetlectusattinciduntscelerisque.Suspendisseegeterateleifend,posuerenuncvenenatis,faucibusdolor.Nuncvitaeluctusmetus.Nullamultriciesarcuvitaeestfermentumeleifend.Suspendisselaoreetmaximuslacus,utlaoreetnisiiaculisvitae.Nullamscelerisqueporttitorpulvinar.Intinciduntipsummauris,velaliquetmetusdictumut.Nunceratelit,suscipitacnisiac,volutpatporttitormauris.Aliquampretiumnisidiam,molestietemporlacusplaceratid.Mauristinciduntmattisturpis,velconvallisurnatempusnon.Integermattismetusnoneuismodcursus.Namideratetmassapretiumfinibus.Praesentfermentumnuncurna,quissagittismaurisimperdieteu.InLoremipsumdolorsitamet,consecteturadipiscingelit.Suspendissemetusnisl,pretiumsedeuismodeget,bibendumeusem.Donecaccumsanrisusnibh,etefficiturnisivehiculatempus.Etiamegestasenimatduieleifendmaximus.Nuncinsemperest.Etiamvelodioultrices,interdumeratsed,dignissimmetus.Phasellusexleo,eleifendquisexid,laciniavenenatisneque.Sednuncdiam,molestieveltinciduntnec,ornareetnisi.Maecenasetmolestietortor.Nullaeupulvinarquam.Aeneanmolestieliberoquistortorviverralobortis.Praesentlaoreetlectusattinciduntscelerisque.Suspendisseegeterateleifend,posuerenuncvenenatis,faucibusdolor.Nuncvitaeluctusmetus.Nullamultriciesarcuvitaeestfermentumeleifend.Suspendisselaoreetmaximuslacus,utlaoreetnisiiaculisvitae.Nullamscelerisqueporttitorpulvinar.Intinciduntipsummauris,velaliquetmetusdictumut.Nunceratelit,suscipitacnisiac,volutpatporttitormauris.Aliquampretiumnisidiam,molestietemporlacusplaceratid.Mauristinciduntmattisturpis,velconvallisurnatempusnon.Integermattismetusnoneuismodcursus.Namideratetmassapretiumfinibus.Praesentfermentumnuncurna,quissagittismaurisimperdieteu.Inefficituraliquamdui.Phasellussempermaurisacconvallismollis.Suspendisseintellusanuncvariusiaculisutegetlibero.Inmalesuada,nislquisconsecteturposuere,nullaipsumfringillaeros,egetrhoncussapienarcunecenim.Proinvenenatistortorveltristiquealiquam.Utelementumtellusligula,velauctorexfermentuma.Vestibulummaximusanteinvulputateornare.Sedquisnislaligulaporttitorfacilisismattissedmi.Crasconsecteturexegetsagittisfeugiat.Invenenatisminectinciduntaliquet.Sedcommodonecorciidvenenatis.Vestibulumanteipsumprimisinfaucibusorciluctusetultricesposuerecubiliacurae;Phasellusinterdumorcefficituraliquamdui.Phasellussempermaurisacconvallismollis.Suspendisseintellusanuncvariusiaculisutegetlibero.Inmalesuada,nislquisconsecteturposuere,nullaipsumfringillaeros,egetrhoncussapienarcunecenim.Proinvenenatistortorveltristiquealiquam.Utelementumtellusligula,velauctorexfermentuma.Vestibulummaximusanteinvulputateornare.Sedquisnislaligulaporttitorfacilisismattissedmi.Crasconsecteturexegetsagittisfeugiat.Invenenatisminectinciduntaliquet.Sedcommodonecorciidvenenatis.Vestibulumanteipsumprimisinfaucibusorciluctusetultricesposuerecubiliacurae;Phasellusinterdumorcrtis.Praesentlaoreetlectusattinciduntscelerisque.Suspendisseegeterateleifend,posuerenuncvenenatis,faucibusdolor.Nuncvitaeluctusmetus.Nullamultriciesarcuvitaeestfermentumeleifend.Suspendisselaoreetmaximuslacus,utlaoreetnisiiaculisvitae.Nullamscelerisqueporttitorpulvinar.Intinciduntipsummauris,velaliquetmetusdictumut.Nunceratelit,suscipitacnisiac,volutpatporttitormauris.Aliquampretiumnisidiam,molestietemporlacusplaceratid.Mauristinciduntmattisturpis,velconvallisurnatempusnon.Integermattismetusnoneuismodcursus.Namideratetmassapretiumfinibus.Praesentfermentumnuncurna,quissagittismaurisimperdieteu.Inefficituraliquamdui.Phasellussempermaurisacconvallismollis.Suspendisseintellusanuncvariusiaculisutegetlibero.Inmalesuada,nislquisconsecteturposuere,nullaipsumfringillaeros,egetrhoncussapienarcunece"; + ctx.dma_tx.as_mut_slice()[0..LONG_TEST_STRING.len()] + .copy_from_slice(&LONG_TEST_STRING.as_bytes()); + ctx.dma_tx.set_length(LONG_TEST_STRING.len()); + + let (uhci_rx, uhci_tx) = ctx.uhci.split(); + let transfer_rx = uhci_rx + .read(ctx.dma_rx) + .unwrap_or_else(|x| panic!("Something went horribly wrong: {:?}", x.0)); + let transfer_tx = uhci_tx + .write(ctx.dma_tx) + .unwrap_or_else(|x| panic!("Something went horribly wrong: {:?}", x.0)); + let (res, _uhci_tx, _dma_tx) = transfer_tx.wait(); + res.unwrap(); + let (res, _uhci_rx, dma_rx) = transfer_rx.wait(); + res.unwrap(); + + assert_eq!( + &dma_rx.as_slice()[0..dma_rx.number_of_received_bytes()], + LONG_TEST_STRING.as_bytes() + ); + } + + #[test] + async fn test_send_receive_async(mut ctx: Context) { + let uhci = ctx.uhci.into_async(); + const SEND: &[u8] = b"Hello ESP32"; + ctx.dma_tx.as_mut_slice()[0..SEND.len()].copy_from_slice(&SEND); + ctx.dma_tx.set_length(SEND.len()); + + let (uhci_rx, uhci_tx) = uhci.split(); + let mut transfer_rx = uhci_rx + .read(ctx.dma_rx) + .unwrap_or_else(|x| panic!("Something went horribly wrong: {:?}", x.0)); + let mut transfer_tx = uhci_tx + .write(ctx.dma_tx) + .unwrap_or_else(|x| panic!("Something went horribly wrong: {:?}", x.0)); + transfer_tx.wait_for_done().await; + let (res, _uhci_tx, _dma_tx) = transfer_tx.wait(); + res.unwrap(); + transfer_rx.wait_for_done().await; + let (res, _uhci_rx, dma_rx) = transfer_rx.wait(); + res.unwrap(); + + assert_eq!( + &dma_rx.as_slice()[0..dma_rx.number_of_received_bytes()], + SEND + ); + } + + #[test] + async fn test_long_strings_async(mut ctx: Context) { + let uhci = ctx.uhci.into_async(); + const LONG_TEST_STRING: &str = "Loremipsumdolorsitamet,consecteturadipiscingelit.Suspendissemetusnisl,pretiumsedeuismodeget,bibendumeusem.Donecaccumsanrisusnibh,etefficiturnisivehiculatempus.Etiamegestasenimatduieleifendmaximus.Nuncinsemperest.Etiamvelodioultrices,interdumeratsed,dignissimmetus.Phasellusexleo,eleifendquisexid,laciniavenenatisneque.Sednuncdiam,molestieveltinciduntnec,ornareetnisi.Maecenasetmolestietortor.Nullaeupulvinarquam.Aeneanmolestieliberoquistortorviverralobortis.Praesentlaoreetlectusattinciduntscelerisque.Suspendisseegeterateleifend,posuerenuncvenenatis,faucibusdolor.Nuncvitaeluctusmetus.Nullamultriciesarcuvitaeestfermentumeleifend.Suspendisselaoreetmaximuslacus,utlaoreetnisiiaculisvitae.Nullamscelerisqueporttitorpulvinar.Intinciduntipsummauris,velaliquetmetusdictumut.Nunceratelit,suscipitacnisiac,volutpatporttitormauris.Aliquampretiumnisidiam,molestietemporlacusplaceratid.Mauristinciduntmattisturpis,velconvallisurnatempusnon.Integermattismetusnoneuismodcursus.Namideratetmassapretiumfinibus.Praesentfermentumnuncurna,quissagittismaurisimperdieteu.InLoremipsumdolorsitamet,consecteturadipiscingelit.Suspendissemetusnisl,pretiumsedeuismodeget,bibendumeusem.Donecaccumsanrisusnibh,etefficiturnisivehiculatempus.Etiamegestasenimatduieleifendmaximus.Nuncinsemperest.Etiamvelodioultrices,interdumeratsed,dignissimmetus.Phasellusexleo,eleifendquisexid,laciniavenenatisneque.Sednuncdiam,molestieveltinciduntnec,ornareetnisi.Maecenasetmolestietortor.Nullaeupulvinarquam.Aeneanmolestieliberoquistortorviverralobortis.Praesentlaoreetlectusattinciduntscelerisque.Suspendisseegeterateleifend,posuerenuncvenenatis,faucibusdolor.Nuncvitaeluctusmetus.Nullamultriciesarcuvitaeestfermentumeleifend.Suspendisselaoreetmaximuslacus,utlaoreetnisiiaculisvitae.Nullamscelerisqueporttitorpulvinar.Intinciduntipsummauris,velaliquetmetusdictumut.Nunceratelit,suscipitacnisiac,volutpatporttitormauris.Aliquampretiumnisidiam,molestietemporlacusplaceratid.Mauristinciduntmattisturpis,velconvallisurnatempusnon.Integermattismetusnoneuismodcursus.Namideratetmassapretiumfinibus.Praesentfermentumnuncurna,quissagittismaurisimperdieteu.Inefficituraliquamdui.Phasellussempermaurisacconvallismollis.Suspendisseintellusanuncvariusiaculisutegetlibero.Inmalesuada,nislquisconsecteturposuere,nullaipsumfringillaeros,egetrhoncussapienarcunecenim.Proinvenenatistortorveltristiquealiquam.Utelementumtellusligula,velauctorexfermentuma.Vestibulummaximusanteinvulputateornare.Sedquisnislaligulaporttitorfacilisismattissedmi.Crasconsecteturexegetsagittisfeugiat.Invenenatisminectinciduntaliquet.Sedcommodonecorciidvenenatis.Vestibulumanteipsumprimisinfaucibusorciluctusetultricesposuerecubiliacurae;Phasellusinterdumorcefficituraliquamdui.Phasellussempermaurisacconvallismollis.Suspendisseintellusanuncvariusiaculisutegetlibero.Inmalesuada,nislquisconsecteturposuere,nullaipsumfringillaeros,egetrhoncussapienarcunecenim.Proinvenenatistortorveltristiquealiquam.Utelementumtellusligula,velauctorexfermentuma.Vestibulummaximusanteinvulputateornare.Sedquisnislaligulaporttitorfacilisismattissedmi.Crasconsecteturexegetsagittisfeugiat.Invenenatisminectinciduntaliquet.Sedcommodonecorciidvenenatis.Vestibulumanteipsumprimisinfaucibusorciluctusetultricesposuerecubiliacurae;Phasellusinterdumorcrtis.Praesentlaoreetlectusattinciduntscelerisque.Suspendisseegeterateleifend,posuerenuncvenenatis,faucibusdolor.Nuncvitaeluctusmetus.Nullamultriciesarcuvitaeestfermentumeleifend.Suspendisselaoreetmaximuslacus,utlaoreetnisiiaculisvitae.Nullamscelerisqueporttitorpulvinar.Intinciduntipsummauris,velaliquetmetusdictumut.Nunceratelit,suscipitacnisiac,volutpatporttitormauris.Aliquampretiumnisidiam,molestietemporlacusplaceratid.Mauristinciduntmattisturpis,velconvallisurnatempusnon.Integermattismetusnoneuismodcursus.Namideratetmassapretiumfinibus.Praesentfermentumnuncurna,quissagittismaurisimperdieteu.Inefficituraliquamdui.Phasellussempermaurisacconvallismollis.Suspendisseintellusanuncvariusiaculisutegetlibero.Inmalesuada,nislquisconsecteturposuere,nullaipsumfringillaeros,egetrhoncussapienarcunece"; + ctx.dma_tx.as_mut_slice()[0..LONG_TEST_STRING.len()] + .copy_from_slice(&LONG_TEST_STRING.as_bytes()); + ctx.dma_tx.set_length(LONG_TEST_STRING.len()); + + let (uhci_rx, uhci_tx) = uhci.split(); + let mut transfer_rx = uhci_rx + .read(ctx.dma_rx) + .unwrap_or_else(|x| panic!("Something went horribly wrong: {:?}", x.0)); + let mut transfer_tx = uhci_tx + .write(ctx.dma_tx) + .unwrap_or_else(|x| panic!("Something went horribly wrong: {:?}", x.0)); + transfer_tx.wait_for_done().await; + let (res, _uhci_tx, _dma_tx) = transfer_tx.wait(); + res.unwrap(); + transfer_rx.wait_for_done().await; + let (res, _uhci_rx, dma_rx) = transfer_rx.wait(); + res.unwrap(); + + assert_eq!( + &dma_rx.as_slice()[0..dma_rx.number_of_received_bytes()], + LONG_TEST_STRING.as_bytes() + ); + } +}