From 2ca1545b5018ae8e5f4deb1c8f1754b7476a28be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 18 Dec 2024 08:32:12 +0100 Subject: [PATCH] SPI: Implement interrupt-driven `transfer_in_place_async` (#2691) * Implement transfer_async * The start of our shiny future * Add State * Wake from interrupt * Rename and remove return value * Fix register write * Fix S2 * Rename traits * Async flushes * Flush before async operations * Fix comments * Rename start fn, place async handler in RAM * Explicitly stop listening before async operations --- esp-hal/CHANGELOG.md | 1 + esp-hal/src/spi/master.rs | 494 +++++++++++++++++++++++++----- hil-test/tests/spi_full_duplex.rs | 130 +++++++- 3 files changed, 538 insertions(+), 87 deletions(-) diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 81876b33a..e7104cd77 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `BurstConfig`, a device-specific configuration for configuring DMA transfers in burst mode (#2543) - `{DmaRxBuf, DmaTxBuf, DmaRxTxBuf}::set_burst_config` (#2543) - ESP32-S2: DMA support for AES (#2699) +- Added `transfer_in_place_async` and embedded-hal-async implementation to `Spi` (#2691) ### Changed diff --git a/esp-hal/src/spi/master.rs b/esp-hal/src/spi/master.rs index 1b9d6447e..b569e567c 100644 --- a/esp-hal/src/spi/master.rs +++ b/esp-hal/src/spi/master.rs @@ -71,17 +71,21 @@ use procmacros::ram; use super::{DmaError, Error, SpiBitOrder, SpiDataMode, SpiMode}; use crate::{ + asynch::AtomicWaker, clock::Clocks, dma::{DmaChannelFor, DmaEligible, DmaRxBuffer, DmaTxBuffer, Rx, Tx}, gpio::{interconnect::PeripheralOutput, InputSignal, NoPin, OutputSignal}, interrupt::InterruptHandler, peripheral::{Peripheral, PeripheralRef}, peripherals::spi2::RegisterBlock, + prelude::InterruptConfigurable, private, + private::Sealed, spi::AnySpi, system::PeripheralGuard, Async, Blocking, + Cpu, Mode, }; @@ -463,12 +467,18 @@ pub struct Spi<'d, Dm, T = AnySpi> { guard: PeripheralGuard, } +impl Sealed for Spi<'_, Dm, T> {} + impl Spi<'_, Dm, T> where T: Instance, + Dm: Mode, { - fn driver(&self) -> &'static Info { - self.spi.info() + fn driver(&self) -> Driver { + Driver { + info: self.spi.info(), + state: self.spi.state(), + } } /// Read a byte from SPI. @@ -502,7 +512,7 @@ where impl<'d> Spi<'d, Blocking> { /// Constructs an SPI instance in 8bit dataframe mode. pub fn new( - spi: impl Peripheral

+ 'd, + spi: impl Peripheral

+ 'd, config: Config, ) -> Result { Self::new_typed(spi.map_into(), config) @@ -514,7 +524,8 @@ where T: Instance, { /// Converts the SPI instance into async mode. - pub fn into_async(self) -> Spi<'d, Async, T> { + pub fn into_async(mut self) -> Spi<'d, Async, T> { + self.set_interrupt_handler(self.spi.handler()); Spi { spi: self.spi, _mode: PhantomData, @@ -536,23 +547,63 @@ where } } +impl InterruptConfigurable for Spi<'_, Blocking, T> +where + T: Instance, +{ + /// Sets the interrupt handler + /// + /// Interrupts are not enabled at the peripheral level here. + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + let interrupt = self.driver().info.interrupt; + for core in Cpu::other() { + crate::interrupt::disable(core, interrupt); + } + unsafe { crate::interrupt::bind_interrupt(interrupt, handler.handler()) }; + unwrap!(crate::interrupt::enable(interrupt, handler.priority())); + } +} + impl<'d, T> Spi<'d, Async, T> where T: Instance, { /// Converts the SPI instance into blocking mode. pub fn into_blocking(self) -> Spi<'d, Blocking, T> { + crate::interrupt::disable(Cpu::current(), self.driver().info.interrupt); Spi { spi: self.spi, _mode: PhantomData, guard: self.guard, } } + + /// Waits for the completion of previous operations. + pub async fn flush_async(&mut self) -> Result<(), Error> { + let driver = self.driver(); + + if !driver.busy() { + return Ok(()); + } + + SpiFuture::new(&driver).await; + + Ok(()) + } + + /// Sends `words` to the slave. Returns the `words` received from the slave + pub async fn transfer_in_place_async(&mut self, words: &mut [u8]) -> Result<(), Error> { + // We need to flush because the blocking transfer functions may return while a + // transfer is still in progress. + self.flush_async().await?; + self.driver().transfer_in_place_async(words).await + } } impl<'d, Dm, T> Spi<'d, Dm, T> where T: Instance, + Dm: Mode, { /// Constructs an SPI instance in 8bit dataframe mode. pub fn new_typed( @@ -578,12 +629,12 @@ where .with_sck(NoPin) .with_cs(NoPin); - let is_qspi = this.driver().sio2_input.is_some(); + let is_qspi = this.driver().info.sio2_input.is_some(); if is_qspi { - unwrap!(this.driver().sio2_input).connect_to(NoPin); - unwrap!(this.driver().sio2_output).connect_to(NoPin); - unwrap!(this.driver().sio3_input).connect_to(NoPin); - unwrap!(this.driver().sio3_output).connect_to(NoPin); + unwrap!(this.driver().info.sio2_input).connect_to(NoPin); + unwrap!(this.driver().info.sio2_output).connect_to(NoPin); + unwrap!(this.driver().info.sio3_input).connect_to(NoPin); + unwrap!(this.driver().info.sio3_output).connect_to(NoPin); } Ok(this) @@ -598,8 +649,8 @@ where mosi.enable_output(true, private::Internal); mosi.enable_input(true, private::Internal); - self.driver().mosi.connect_to(&mut mosi); - self.driver().sio0_input.connect_to(&mut mosi); + self.driver().info.mosi.connect_to(&mut mosi); + self.driver().info.sio0_input.connect_to(&mut mosi); self } @@ -613,8 +664,8 @@ where miso.enable_input(true, private::Internal); miso.enable_output(true, private::Internal); - self.driver().miso.connect_to(&mut miso); - self.driver().sio1_output.connect_to(&mut miso); + self.driver().info.miso.connect_to(&mut miso); + self.driver().info.sio1_output.connect_to(&mut miso); self } @@ -626,7 +677,7 @@ where pub fn with_sck(self, sclk: impl Peripheral

+ 'd) -> Self { crate::into_mapped_ref!(sclk); sclk.set_to_push_pull_output(private::Internal); - self.driver().sclk.connect_to(sclk); + self.driver().info.sclk.connect_to(sclk); self } @@ -639,7 +690,7 @@ where pub fn with_cs(self, cs: impl Peripheral

+ 'd) -> Self { crate::into_mapped_ref!(cs); cs.set_to_push_pull_output(private::Internal); - self.driver().cs.connect_to(cs); + self.driver().info.cs.connect_to(cs); self } @@ -667,7 +718,8 @@ where impl<'d, Dm, T> Spi<'d, Dm, T> where - T: QspiInstance, + T: Instance + QspiInstance, + Dm: Mode, { /// Assign the SIO2 pin for the SPI instance. /// @@ -681,8 +733,8 @@ where sio2.enable_input(true, private::Internal); sio2.enable_output(true, private::Internal); - unwrap!(self.driver().sio2_input).connect_to(&mut sio2); - unwrap!(self.driver().sio2_output).connect_to(&mut sio2); + unwrap!(self.driver().info.sio2_input).connect_to(&mut sio2); + unwrap!(self.driver().info.sio2_output).connect_to(&mut sio2); self } @@ -699,8 +751,8 @@ where sio3.enable_input(true, private::Internal); sio3.enable_output(true, private::Internal); - unwrap!(self.driver().sio3_input).connect_to(&mut sio3); - unwrap!(self.driver().sio3_output).connect_to(&mut sio3); + unwrap!(self.driver().info.sio3_input).connect_to(&mut sio3); + unwrap!(self.driver().info.sio3_output).connect_to(&mut sio3); self } @@ -709,6 +761,7 @@ where impl Spi<'_, Dm, T> where T: Instance, + Dm: Mode, { /// Half-duplex read. #[instability::unstable] @@ -910,7 +963,7 @@ mod dma { /// /// Interrupts are not enabled at the peripheral level here. fn set_interrupt_handler(&mut self, handler: InterruptHandler) { - let interrupt = self.driver().interrupt; + let interrupt = self.driver().info.interrupt; for core in crate::Cpu::other() { crate::interrupt::disable(core, interrupt); } @@ -991,16 +1044,19 @@ mod dma { impl<'d, Dm, T> SpiDma<'d, Dm, T> where - Dm: Mode, T: Instance, + Dm: Mode, { - fn driver(&self) -> &'static Info { - self.spi.info() + fn driver(&self) -> Driver { + Driver { + info: self.spi.info(), + state: self.spi.state(), + } } fn dma_driver(&self) -> DmaDriver { DmaDriver { - info: self.driver(), + driver: self.driver(), dma_peripheral: self.spi.dma_peripheral(), } } @@ -1522,7 +1578,6 @@ mod dma { pub struct SpiDmaBus<'d, Dm, T = AnySpi> where T: Instance, - Dm: Mode, { spi_dma: SpiDma<'d, Dm, T>, @@ -2070,6 +2125,7 @@ mod dma { mod ehal1 { use embedded_hal::spi::SpiBus; + use embedded_hal_async::spi::SpiBus as SpiBusAsync; use embedded_hal_nb::spi::FullDuplex; use super::*; @@ -2081,6 +2137,7 @@ mod ehal1 { impl FullDuplex for Spi<'_, Dm, T> where T: Instance, + Dm: Mode, { fn read(&mut self) -> nb::Result { self.driver().read_byte() @@ -2094,6 +2151,7 @@ mod ehal1 { impl SpiBus for Spi<'_, Dm, T> where T: Instance, + Dm: Mode, { fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { self.driver().read_bytes(words) @@ -2150,29 +2208,95 @@ mod ehal1 { } fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - // Since we have the traits so neatly implemented above, use them! - for chunk in words.chunks_mut(FIFO_SIZE) { - SpiBus::write(self, chunk)?; - SpiBus::flush(self)?; - self.driver().read_bytes_from_fifo(chunk)?; - } - Ok(()) + self.driver().transfer(words).map(|_| ()) } fn flush(&mut self) -> Result<(), Self::Error> { self.driver().flush() } } + + impl SpiBusAsync for Spi<'_, Async, T> + where + T: Instance, + { + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + // We need to flush because the blocking transfer functions may return while a + // transfer is still in progress. + self.flush_async().await?; + self.driver().read_bytes_async(words).await + } + + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + // We need to flush because the blocking transfer functions may return while a + // transfer is still in progress. + self.flush_async().await?; + self.driver().write_bytes_async(words).await + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + // Optimizations + if read.is_empty() { + return SpiBusAsync::write(self, write).await; + } else if write.is_empty() { + return SpiBusAsync::read(self, read).await; + } + + let mut write_from = 0; + let mut read_from = 0; + + loop { + // How many bytes we write in this chunk + let write_inc = core::cmp::min(FIFO_SIZE, write.len() - write_from); + let write_to = write_from + write_inc; + // How many bytes we read in this chunk + let read_inc = core::cmp::min(FIFO_SIZE, read.len() - read_from); + let read_to = read_from + read_inc; + + if (write_inc == 0) && (read_inc == 0) { + break; + } + + // No need to flush here, `SpiBusAsync::write` will do it for us + + if write_to < read_to { + // Read more than we write, must pad writing part with zeros + let mut empty = [EMPTY_WRITE_PAD; FIFO_SIZE]; + empty[0..write_inc].copy_from_slice(&write[write_from..write_to]); + SpiBusAsync::write(self, &empty).await?; + } else { + SpiBusAsync::write(self, &write[write_from..write_to]).await?; + } + + if read_inc > 0 { + self.driver() + .read_bytes_from_fifo(&mut read[read_from..read_to])?; + } + + write_from = write_to; + read_from = read_to; + } + Ok(()) + } + + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.transfer_in_place_async(words).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_async().await + } + } } /// SPI peripheral instance. -pub trait Instance: private::Sealed + Into + DmaEligible + 'static { +pub trait PeripheralInstance: private::Sealed + Into + DmaEligible + 'static { /// Returns the peripheral data describing this SPI instance. fn info(&self) -> &'static Info; } /// Marker trait for QSPI-capable SPI peripherals. -pub trait QspiInstance: Instance {} +pub trait QspiInstance: PeripheralInstance {} /// Peripheral data describing a particular SPI instance. #[non_exhaustive] @@ -2220,14 +2344,14 @@ pub struct Info { } struct DmaDriver { - info: &'static Info, + driver: Driver, dma_peripheral: crate::dma::DmaPeripheral, } impl DmaDriver { fn abort_transfer(&self) { - self.info.configure_datalen(1, 1); - self.info.update(); + self.driver.configure_datalen(1, 1); + self.driver.update(); } #[allow(clippy::too_many_arguments)] @@ -2242,7 +2366,7 @@ impl DmaDriver { rx: &mut RX, tx: &mut TX, ) -> Result<(), Error> { - let reg_block = self.info.register_block(); + let reg_block = self.driver.register_block(); #[cfg(esp32s2)] { @@ -2251,7 +2375,7 @@ impl DmaDriver { reg_block.dma_in_link().write(|w| w.bits(0)); } - self.info.configure_datalen(rx_len, tx_len); + self.driver.configure_datalen(rx_len, tx_len); // enable the MISO and MOSI if needed reg_block @@ -2286,7 +2410,7 @@ impl DmaDriver { #[cfg(gdma)] self.reset_dma(); - self.info.start_operation(); + self.driver.start_operation(); Ok(()) } @@ -2295,7 +2419,7 @@ impl DmaDriver { #[cfg(gdma)] { // for non GDMA this is done in `assign_tx_device` / `assign_rx_device` - let reg_block = self.info.register_block(); + let reg_block = self.driver.register_block(); reg_block.dma_conf().modify(|_, w| { w.dma_tx_ena().set_bit(); w.dma_rx_ena().set_bit() @@ -2321,7 +2445,7 @@ impl DmaDriver { w.dma_afifo_rst().bit(bit) }); } - let reg_block = self.info.register_block(); + let reg_block = self.driver.register_block(); set_reset_bit(reg_block, true); set_reset_bit(reg_block, false); self.clear_dma_interrupts(); @@ -2329,7 +2453,7 @@ impl DmaDriver { #[cfg(gdma)] fn clear_dma_interrupts(&self) { - let reg_block = self.info.register_block(); + let reg_block = self.driver.register_block(); reg_block.dma_int_clr().write(|w| { w.dma_infifo_full_err().clear_bit_by_one(); w.dma_outfifo_empty_err().clear_bit_by_one(); @@ -2341,7 +2465,7 @@ impl DmaDriver { #[cfg(pdma)] fn clear_dma_interrupts(&self) { - let reg_block = self.info.register_block(); + let reg_block = self.driver.register_block(); reg_block.dma_int_clr().write(|w| { w.inlink_dscr_empty().clear_bit_by_one(); w.outlink_dscr_error().clear_bit_by_one(); @@ -2356,12 +2480,17 @@ impl DmaDriver { } } +struct Driver { + info: &'static Info, + state: &'static State, +} + // private implementation bits // FIXME: split this up into peripheral versions -impl Info { +impl Driver { /// Returns the register block for this SPI instance. pub fn register_block(&self) -> &RegisterBlock { - unsafe { &*self.register_block } + unsafe { &*self.info.register_block } } /// Initialize for full-duplex 1 bit mode @@ -2731,14 +2860,37 @@ impl Info { Ok(()) } + #[cfg_attr(place_spi_driver_in_ram, ram)] + fn fill_fifo(&self, chunk: &[u8]) { + // TODO: replace with `array_chunks` and `from_le_bytes` + let mut c_iter = chunk.chunks_exact(4); + let mut w_iter = self.register_block().w_iter(); + for c in c_iter.by_ref() { + if let Some(w_reg) = w_iter.next() { + let word = + (c[0] as u32) | (c[1] as u32) << 8 | (c[2] as u32) << 16 | (c[3] as u32) << 24; + w_reg.write(|w| w.buf().set(word)); + } + } + let rem = c_iter.remainder(); + if !rem.is_empty() { + if let Some(w_reg) = w_iter.next() { + let word = match rem.len() { + 3 => (rem[0] as u32) | (rem[1] as u32) << 8 | (rem[2] as u32) << 16, + 2 => (rem[0] as u32) | (rem[1] as u32) << 8, + 1 => rem[0] as u32, + _ => unreachable!(), + }; + w_reg.write(|w| w.buf().set(word)); + } + } + } + /// Write bytes to SPI. /// - /// Copies the content of `words` in chunks of 64 bytes into the SPI - /// transmission FIFO. If `words` is longer than 64 bytes, multiple - /// sequential transfers are performed. This function will return before - /// all bytes of the last chunk to transmit have been sent to the wire. If - /// you must ensure that the whole messages was written correctly, use - /// [`Self::flush`]. + /// This function will return before all bytes of the last chunk to transmit + /// have been sent to the wire. If you must ensure that the whole + /// messages was written correctly, use [`Self::flush`]. #[cfg_attr(place_spi_driver_in_ram, ram)] fn write_bytes(&self, words: &[u8]) -> Result<(), Error> { let num_chunks = words.len() / FIFO_SIZE; @@ -2751,33 +2903,7 @@ impl Info { // transmitted for (i, chunk) in words.chunks(FIFO_SIZE).enumerate() { self.configure_datalen(0, chunk.len()); - - { - // TODO: replace with `array_chunks` and `from_le_bytes` - let mut c_iter = chunk.chunks_exact(4); - let mut w_iter = self.register_block().w_iter(); - for c in c_iter.by_ref() { - if let Some(w_reg) = w_iter.next() { - let word = (c[0] as u32) - | (c[1] as u32) << 8 - | (c[2] as u32) << 16 - | (c[3] as u32) << 24; - w_reg.write(|w| w.buf().set(word)); - } - } - let rem = c_iter.remainder(); - if !rem.is_empty() { - if let Some(w_reg) = w_iter.next() { - let word = match rem.len() { - 3 => (rem[0] as u32) | (rem[1] as u32) << 8 | (rem[2] as u32) << 16, - 2 => (rem[0] as u32) | (rem[1] as u32) << 8, - 1 => rem[0] as u32, - _ => unreachable!(), - }; - w_reg.write(|w| w.buf().set(word)); - } - } - } + self.fill_fifo(chunk); self.start_operation(); @@ -2791,6 +2917,19 @@ impl Info { Ok(()) } + /// Write bytes to SPI. + #[cfg_attr(place_spi_driver_in_ram, ram)] + async fn write_bytes_async(&self, words: &[u8]) -> Result<(), Error> { + // The fifo has a limited fixed size, so the data must be chunked and then + // transmitted + for chunk in words.chunks(FIFO_SIZE) { + self.configure_datalen(0, chunk.len()); + self.fill_fifo(chunk); + self.execute_operation_async().await; + } + Ok(()) + } + /// Read bytes from SPI. /// /// Sends out a stuffing byte for every byte to read. This function doesn't @@ -2808,6 +2947,22 @@ impl Info { Ok(()) } + /// Read bytes from SPI. + /// + /// Sends out a stuffing byte for every byte to read. This function doesn't + /// perform flushing. If you want to read the response to something you + /// have written before, consider using [`Self::transfer`] instead. + #[cfg_attr(place_spi_driver_in_ram, ram)] + async fn read_bytes_async(&self, words: &mut [u8]) -> Result<(), Error> { + let empty_array = [EMPTY_WRITE_PAD; FIFO_SIZE]; + + for chunk in words.chunks_mut(FIFO_SIZE) { + self.write_bytes_async(&empty_array[0..chunk.len()]).await?; + self.read_bytes_from_fifo(chunk)?; + } + Ok(()) + } + /// Read received bytes from SPI FIFO. /// /// Copies the contents of the SPI receive FIFO into `words`. This function @@ -2833,6 +2988,59 @@ impl Info { Ok(()) } + // FIXME EnumSet flag + fn clear_done_interrupt(&self) { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2))] { + self.register_block().slave().modify(|_, w| { + w.trans_done().clear_bit() + }); + } else { + self.register_block().dma_int_clr().write(|w| { + w.trans_done().clear_bit_by_one() + }); + } + } + } + + fn is_transfer_done(&self) -> bool { + let reg_block = self.register_block(); + + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2))] { + reg_block.slave().read().trans_done().bit_is_set() + } else { + reg_block.dma_int_raw().read().trans_done().bit_is_set() + } + } + } + + fn enable_listen_for_trans_done(&self, enable: bool) { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + self.register_block().slave().modify(|_, w| { + w.trans_inten().bit(enable) + }); + } else if #[cfg(esp32s2)] { + self.register_block().slave().modify(|_, w| { + w.int_trans_done_en().bit(enable) + }); + } else { + self.register_block().dma_int_ena().write(|w| { + w.trans_done().bit(enable) + }); + } + } + } + + fn start_listening(&self) { + self.enable_listen_for_trans_done(true); + } + + fn stop_listening(&self) { + self.enable_listen_for_trans_done(false); + } + fn busy(&self) -> bool { let reg_block = self.register_block(); reg_block.cmd().read().usr().bit_is_set() @@ -2857,12 +3065,30 @@ impl Info { Ok(words) } + #[cfg_attr(place_spi_driver_in_ram, ram)] + async fn transfer_in_place_async(&self, words: &mut [u8]) -> Result<(), Error> { + for chunk in words.chunks_mut(FIFO_SIZE) { + self.write_bytes_async(chunk).await?; + self.read_bytes_from_fifo(chunk)?; + } + + Ok(()) + } + fn start_operation(&self) { let reg_block = self.register_block(); self.update(); reg_block.cmd().modify(|_, w| w.usr().set_bit()); } + /// Starts the operation and waits for it to complete. + async fn execute_operation_async(&self) { + self.stop_listening(); + self.clear_done_interrupt(); + self.start_operation(); + SpiFuture::new(self).await; + } + #[allow(clippy::too_many_arguments)] fn setup_half_duplex( &self, @@ -3012,10 +3238,13 @@ impl PartialEq for Info { unsafe impl Sync for Info {} +// TODO: this macro needs to move to one level up, and it needs to describe the +// hardware fully. The master module should extend it with the master specific +// details. macro_rules! spi_instance { ($num:literal, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident $(, $sio2:ident, $sio3:ident)?) => { paste::paste! { - impl Instance for crate::peripherals::[] { + impl PeripheralInstance for crate::peripherals::[] { #[inline(always)] fn info(&self) -> &'static Info { static INFO: Info = Info { @@ -3069,7 +3298,7 @@ cfg_if::cfg_if! { } } -impl Instance for super::AnySpi { +impl PeripheralInstance for super::AnySpi { delegate::delegate! { to match &self.0 { super::AnySpiInner::Spi2(spi) => spi, @@ -3082,3 +3311,104 @@ impl Instance for super::AnySpi { } impl QspiInstance for super::AnySpi {} + +#[doc(hidden)] +pub struct State { + waker: AtomicWaker, +} + +#[cfg_attr(place_spi_driver_in_ram, ram)] +fn handle_async(instance: I) { + let state = instance.state(); + let info = instance.info(); + + let driver = Driver { info, state }; + if driver.is_transfer_done() { + state.waker.wake(); + driver.stop_listening(); + } +} + +#[doc(hidden)] +pub trait Instance: PeripheralInstance { + fn state(&self) -> &'static State; + fn handler(&self) -> InterruptHandler; +} + +macro_rules! master_instance { + ($peri:ident) => { + impl Instance for $crate::peripherals::$peri { + fn state(&self) -> &'static State { + static STATE: State = State { + waker: AtomicWaker::new(), + }; + + &STATE + } + + fn handler(&self) -> InterruptHandler { + #[$crate::macros::handler] + #[cfg_attr(place_spi_driver_in_ram, ram)] + fn handle() { + handle_async(unsafe { $crate::peripherals::$peri::steal() }) + } + + handle + } + } + }; +} + +master_instance!(SPI2); +#[cfg(spi3)] +master_instance!(SPI3); + +impl Instance for super::AnySpi { + delegate::delegate! { + to match &self.0 { + super::AnySpiInner::Spi2(spi) => spi, + #[cfg(spi3)] + super::AnySpiInner::Spi3(spi) => spi, + } { + fn state(&self) -> &'static State; + fn handler(&self) -> InterruptHandler; + } + } +} + +struct SpiFuture<'a> { + driver: &'a Driver, +} + +impl<'a> SpiFuture<'a> { + pub fn new(driver: &'a Driver) -> Self { + Self { driver } + } +} + +use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +impl Future for SpiFuture<'_> { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.driver.is_transfer_done() { + self.driver.clear_done_interrupt(); + return Poll::Ready(()); + } + + self.driver.state.waker.register(cx.waker()); + self.driver.start_listening(); + Poll::Pending + } +} + +impl Drop for SpiFuture<'_> { + fn drop(&mut self) { + self.driver.stop_listening(); + } +} diff --git a/hil-test/tests/spi_full_duplex.rs b/hil-test/tests/spi_full_duplex.rs index 9c587ef6f..cc8a4b5ff 100644 --- a/hil-test/tests/spi_full_duplex.rs +++ b/hil-test/tests/spi_full_duplex.rs @@ -9,7 +9,6 @@ #![no_main] use embedded_hal::spi::SpiBus; -#[cfg(pcnt)] use embedded_hal_async::spi::SpiBus as SpiBusAsync; use esp_hal::{ dma::{DmaDescriptor, DmaRxBuf, DmaTxBuf}, @@ -107,6 +106,18 @@ mod tests { assert_eq!(write, read); } + #[test] + async fn test_async_symmetric_transfer(ctx: Context) { + let write = [0xde, 0xad, 0xbe, 0xef]; + let mut read: [u8; 4] = [0x00u8; 4]; + + let mut spi = ctx.spi.into_async(); + SpiBusAsync::transfer(&mut spi, &mut read[..], &write[..]) + .await + .expect("Symmetric transfer failed"); + assert_eq!(write, read); + } + #[test] fn test_asymmetric_transfer(mut ctx: Context) { let write = [0xde, 0xad, 0xbe, 0xef]; @@ -118,6 +129,19 @@ mod tests { assert_eq!(read[2], 0x00u8); } + #[test] + async fn test_async_asymmetric_transfer(ctx: Context) { + let write = [0xde, 0xad, 0xbe, 0xef]; + let mut read: [u8; 4] = [0x00; 4]; + + let mut spi = ctx.spi.into_async(); + SpiBusAsync::transfer(&mut spi, &mut read[0..2], &write[..]) + .await + .expect("Asymmetric transfer failed"); + assert_eq!(write[0], read[0]); + assert_eq!(read[2], 0x00u8); + } + #[test] #[cfg(pcnt)] fn test_asymmetric_write(mut ctx: Context) { @@ -136,6 +160,50 @@ mod tests { assert_eq!(unit.value(), 9); } + #[test] + #[cfg(pcnt)] + async fn test_async_asymmetric_write(ctx: Context) { + let write = [0xde, 0xad, 0xbe, 0xef]; + + let unit = ctx.pcnt_unit; + + unit.channel0.set_edge_signal(ctx.pcnt_source); + unit.channel0 + .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); + + let mut spi = ctx.spi.into_async(); + SpiBusAsync::write(&mut spi, &write[..]) + .await + .expect("Asymmetric write failed"); + + assert_eq!(unit.value(), 9); + } + + #[test] + #[cfg(pcnt)] + async fn async_write_after_sync_write_waits_for_flush(ctx: Context) { + let write = [0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef]; + + let unit = ctx.pcnt_unit; + + unit.channel0.set_edge_signal(ctx.pcnt_source); + unit.channel0 + .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); + + let mut spi = ctx.spi.into_async(); + + // Slow down SCLK so that transferring the buffer takes a while. + spi.apply_config(&Config::default().with_frequency(80.kHz())) + .expect("Apply config failed"); + + SpiBus::write(&mut spi, &write[..]).expect("Sync write failed"); + SpiBusAsync::write(&mut spi, &write[..]) + .await + .expect("Async write failed"); + + assert_eq!(unit.value(), 34); + } + #[test] #[cfg(pcnt)] fn test_asymmetric_write_transfer(mut ctx: Context) { @@ -154,21 +222,57 @@ mod tests { assert_eq!(unit.value(), 9); } + #[test] + #[cfg(pcnt)] + async fn test_async_asymmetric_write_transfer(ctx: Context) { + let write = [0xde, 0xad, 0xbe, 0xef]; + + let unit = ctx.pcnt_unit; + + unit.channel0.set_edge_signal(ctx.pcnt_source); + unit.channel0 + .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); + + let mut spi = ctx.spi.into_async(); + SpiBusAsync::transfer(&mut spi, &mut [], &write[..]) + .await + .expect("Asymmetric transfer failed"); + + assert_eq!(unit.value(), 9); + } + #[test] fn test_symmetric_transfer_huge_buffer(mut ctx: Context) { - let mut write = [0x55u8; 4096]; + let write = &mut ctx.tx_buffer[0..4096]; for byte in 0..write.len() { write[byte] = byte as u8; } - let mut read = [0x00u8; 4096]; + let read = &mut ctx.rx_buffer[0..4096]; SpiBus::transfer(&mut ctx.spi, &mut read[..], &write[..]).expect("Huge transfer failed"); assert_eq!(write, read); } #[test] - fn test_symmetric_transfer_huge_buffer_no_alloc(mut ctx: Context) { - let mut write = [0x55u8; 4096]; + async fn test_async_symmetric_transfer_huge_buffer(ctx: Context) { + let write = &mut ctx.tx_buffer[0..4096]; + for byte in 0..write.len() { + write[byte] = byte as u8; + } + let read = &mut ctx.rx_buffer[0..4096]; + + let mut spi = ctx.spi.into_async(); + SpiBusAsync::transfer(&mut spi, &mut read[..], &write[..]) + .await + .expect("Huge transfer failed"); + for idx in 0..write.len() { + assert_eq!(write[idx], read[idx], "Mismatch at index {}", idx); + } + } + + #[test] + fn test_symmetric_transfer_huge_buffer_in_place(mut ctx: Context) { + let write = &mut ctx.tx_buffer[0..4096]; for byte in 0..write.len() { write[byte] = byte as u8; } @@ -181,6 +285,22 @@ mod tests { } } + #[test] + async fn test_async_symmetric_transfer_huge_buffer_in_place(ctx: Context) { + let write = &mut ctx.tx_buffer[0..4096]; + for byte in 0..write.len() { + write[byte] = byte as u8; + } + + let mut spi = ctx.spi.into_async(); + SpiBusAsync::transfer_in_place(&mut spi, &mut write[..]) + .await + .expect("Huge transfer failed"); + for byte in 0..write.len() { + assert_eq!(write[byte], byte as u8); + } + } + #[test] #[cfg(pcnt)] fn test_dma_read_dma_write_pcnt(ctx: Context) {