Skip to content

Commit bc28f64

Browse files
authored
SPI: stop transfer if Spi::transfer_in_place is cancelled (#3242)
* Move OnDrop * Abort async transfer * Finish sentence
1 parent eaa7f70 commit bc28f64

File tree

5 files changed

+136
-49
lines changed

5 files changed

+136
-49
lines changed

esp-hal/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2828
- `Uart::{with_tx, with_rx}` can now be called on the async driver as well (#3212)
2929
- ESP32: Fixed SPI3 QSPI signals (#3201)
3030
- ESP32-C6/H2: The `flip_link` feature should no longer crash (#3203)
31-
31+
- SPI: `Spi::transfer_in_place_async` now stops the transfer when cancelled (#3242)
3232
- ESP32/ESP32-S2: Avoid running into timeouts with reads/writes larger than the FIFO (#3199)
3333

3434
- ESP32-C6: Keep ADC enabled to improve radio signal strength (#3249)

esp-hal/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,8 @@ impl crate::private::Sealed for Blocking {}
367367
impl crate::private::Sealed for Async {}
368368

369369
pub(crate) mod private {
370+
use core::mem::ManuallyDrop;
371+
370372
pub trait Sealed {}
371373

372374
#[non_exhaustive]
@@ -390,6 +392,23 @@ pub(crate) mod private {
390392
Self
391393
}
392394
}
395+
396+
pub(crate) struct OnDrop<F: FnOnce()>(ManuallyDrop<F>);
397+
impl<F: FnOnce()> OnDrop<F> {
398+
pub fn new(cb: F) -> Self {
399+
Self(ManuallyDrop::new(cb))
400+
}
401+
402+
pub fn defuse(self) {
403+
core::mem::forget(self);
404+
}
405+
}
406+
407+
impl<F: FnOnce()> Drop for OnDrop<F> {
408+
fn drop(&mut self) {
409+
unsafe { (ManuallyDrop::take(&mut self.0))() }
410+
}
411+
}
393412
}
394413

395414
#[cfg(feature = "unstable")]

esp-hal/src/spi/master.rs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ use crate::{
5959
interrupt::InterruptHandler,
6060
pac::spi2::RegisterBlock,
6161
peripheral::{Peripheral, PeripheralRef},
62-
private::{self, Sealed},
62+
private::{self, OnDrop, Sealed},
6363
spi::AnySpi,
6464
system::{Cpu, PeripheralGuard},
6565
time::Rate,
@@ -849,7 +849,12 @@ impl<'d> Spi<'d, Async> {
849849
Ok(())
850850
}
851851

852-
/// Sends `words` to the slave. Returns the `words` received from the slave
852+
/// Sends `words` to the slave. Returns the `words` received from the slave.
853+
///
854+
/// This function aborts the transfer when its Future is dropped. Some
855+
/// amount of data may have been transferred before the Future is
856+
/// dropped. Dropping the future may block for a short while to ensure
857+
/// the transfer is aborted.
853858
pub async fn transfer_in_place_async(&mut self, words: &mut [u8]) -> Result<(), Error> {
854859
// We need to flush because the blocking transfer functions may return while a
855860
// transfer is still in progress.
@@ -2871,6 +2876,22 @@ impl Driver {
28712876
unsafe { &*self.info.register_block }
28722877
}
28732878

2879+
fn abort_transfer(&self) {
2880+
// Note(danielb): This method came later than DmaDriver::abort_transfer. That
2881+
// function works for DMA so I have left it unchanged, but does not work
2882+
// for CPU-controlled transfers on the ESP32. Toggling slave mode works on
2883+
// ESP32, but not on later chips.
2884+
cfg_if::cfg_if! {
2885+
if #[cfg(esp32)] {
2886+
self.regs().slave().modify(|_, w| w.mode().set_bit());
2887+
self.regs().slave().modify(|_, w| w.mode().clear_bit());
2888+
} else {
2889+
self.configure_datalen(1, 1);
2890+
}
2891+
}
2892+
self.update();
2893+
}
2894+
28742895
/// Initialize for full-duplex 1 bit mode
28752896
fn init(&self) {
28762897
self.regs().user().modify(|_, w| {
@@ -3390,8 +3411,7 @@ impl Driver {
33903411
}
33913412

33923413
fn busy(&self) -> bool {
3393-
let reg_block = self.regs();
3394-
reg_block.cmd().read().usr().bit_is_set()
3414+
self.regs().cmd().read().usr().bit_is_set()
33953415
}
33963416

33973417
// Check if the bus is busy and if it is wait for it to be idle
@@ -3416,7 +3436,16 @@ impl Driver {
34163436
#[cfg_attr(place_spi_driver_in_ram, ram)]
34173437
async fn transfer_in_place_async(&self, words: &mut [u8]) -> Result<(), Error> {
34183438
for chunk in words.chunks_mut(FIFO_SIZE) {
3419-
self.write_async(chunk).await?;
3439+
// Cut the transfer short if the future is dropped. We'll block for a short
3440+
// while to ensure the peripheral is idle.
3441+
let cancel_on_drop = OnDrop::new(|| {
3442+
self.abort_transfer();
3443+
while self.busy() {}
3444+
});
3445+
let res = self.write_async(chunk).await;
3446+
cancel_on_drop.defuse();
3447+
res?;
3448+
34203449
self.read_from_fifo(chunk)?;
34213450
}
34223451

esp-hal/src/uart.rs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ use crate::{
6262
pac::uart0::RegisterBlock,
6363
peripheral::{Peripheral, PeripheralRef},
6464
peripherals::Interrupt,
65+
private::OnDrop,
6566
system::{PeripheralClockControl, PeripheralGuard},
6667
Async,
6768
Blocking,
@@ -3052,18 +3053,3 @@ impl Instance for AnyUart {
30523053
}
30533054
}
30543055
}
3055-
3056-
struct OnDrop<F: FnOnce()>(Option<F>);
3057-
impl<F: FnOnce()> OnDrop<F> {
3058-
fn new(cb: F) -> Self {
3059-
Self(Some(cb))
3060-
}
3061-
}
3062-
3063-
impl<F: FnOnce()> Drop for OnDrop<F> {
3064-
fn drop(&mut self) {
3065-
if let Some(cb) = self.0.take() {
3066-
cb();
3067-
}
3068-
}
3069-
}

hil-test/tests/spi_full_duplex.rs

Lines changed: 81 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use embedded_hal::spi::SpiBus;
1313
use embedded_hal_async::spi::SpiBus as SpiBusAsync;
1414
use esp_hal::{
15+
gpio::Input,
16+
peripheral::Peripheral,
1517
spi::master::{Config, Spi},
1618
time::Rate,
1719
Blocking,
@@ -26,10 +28,7 @@ cfg_if::cfg_if! {
2628
gpio::{Level, NoPin},
2729
};
2830
#[cfg(pcnt)]
29-
use esp_hal::{
30-
gpio::interconnect::InputSignal,
31-
pcnt::{channel::EdgeMode, unit::Unit, Pcnt},
32-
};
31+
use esp_hal::pcnt::{channel::EdgeMode, unit::Unit, Pcnt};
3332
}
3433
}
3534

@@ -53,8 +52,7 @@ struct Context {
5352
tx_buffer: &'static mut [u8],
5453
#[cfg(feature = "unstable")]
5554
tx_descriptors: &'static mut [DmaDescriptor],
56-
#[cfg(all(pcnt, feature = "unstable"))]
57-
pcnt_source: InputSignal,
55+
miso_input: Input<'static>,
5856
#[cfg(all(pcnt, feature = "unstable"))]
5957
pcnt_unit: Unit<'static, 0>,
6058
}
@@ -70,7 +68,13 @@ mod tests {
7068
esp_hal::Config::default().with_cpu_clock(esp_hal::clock::CpuClock::max()),
7169
);
7270

73-
let (_, mosi) = hil_test::common_test_pins!(peripherals);
71+
let (_, miso) = hil_test::common_test_pins!(peripherals);
72+
73+
// A bit ugly but the peripheral interconnect APIs aren't yet stable.
74+
let mosi = unsafe { miso.clone_unchecked() };
75+
let miso_input = unsafe { miso.clone_unchecked() };
76+
// Will be used later to detect edges directly or through PCNT.
77+
let miso_input = Input::new(miso_input, Default::default());
7478

7579
#[cfg(feature = "unstable")]
7680
cfg_if::cfg_if! {
@@ -83,16 +87,8 @@ mod tests {
8387

8488
cfg_if::cfg_if! {
8589
if #[cfg(feature = "unstable")] {
86-
let (miso, mosi) = mosi.split();
87-
88-
#[cfg(pcnt)]
89-
let mosi_loopback_pcnt = miso.clone();
90-
9190
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(32000);
9291
} else {
93-
use esp_hal::peripheral::Peripheral;
94-
let miso = unsafe { mosi.clone_unchecked() };
95-
9692
static mut TX_BUFFER: [u8; 4096] = [0; 4096];
9793
static mut RX_BUFFER: [u8; 4096] = [0; 4096];
9894

@@ -121,19 +117,19 @@ mod tests {
121117
spi,
122118
rx_buffer,
123119
tx_buffer,
120+
miso_input,
124121
dma_channel,
125122
rx_descriptors,
126123
tx_descriptors,
127124
#[cfg(pcnt)]
128-
pcnt_source: mosi_loopback_pcnt,
129-
#[cfg(pcnt)]
130125
pcnt_unit: pcnt.unit0,
131126
}
132127
} else {
133128
Context {
134129
spi,
135130
rx_buffer,
136131
tx_buffer,
132+
miso_input,
137133
}
138134
}
139135
}
@@ -192,7 +188,8 @@ mod tests {
192188

193189
let unit = ctx.pcnt_unit;
194190

195-
unit.channel0.set_edge_signal(ctx.pcnt_source);
191+
unit.channel0
192+
.set_edge_signal(ctx.miso_input.peripheral_input());
196193
unit.channel0
197194
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
198195

@@ -210,7 +207,8 @@ mod tests {
210207

211208
let unit = ctx.pcnt_unit;
212209

213-
unit.channel0.set_edge_signal(ctx.pcnt_source);
210+
unit.channel0
211+
.set_edge_signal(ctx.miso_input.peripheral_input());
214212
unit.channel0
215213
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
216214

@@ -229,7 +227,8 @@ mod tests {
229227

230228
let unit = ctx.pcnt_unit;
231229

232-
unit.channel0.set_edge_signal(ctx.pcnt_source);
230+
unit.channel0
231+
.set_edge_signal(ctx.miso_input.peripheral_input());
233232
unit.channel0
234233
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
235234

@@ -254,7 +253,8 @@ mod tests {
254253

255254
let unit = ctx.pcnt_unit;
256255

257-
unit.channel0.set_edge_signal(ctx.pcnt_source);
256+
unit.channel0
257+
.set_edge_signal(ctx.miso_input.peripheral_input());
258258
unit.channel0
259259
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
260260

@@ -272,7 +272,8 @@ mod tests {
272272

273273
let unit = ctx.pcnt_unit;
274274

275-
unit.channel0.set_edge_signal(ctx.pcnt_source);
275+
unit.channel0
276+
.set_edge_signal(ctx.miso_input.peripheral_input());
276277
unit.channel0
277278
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
278279

@@ -356,7 +357,8 @@ mod tests {
356357
let unit = ctx.pcnt_unit;
357358
let mut spi = ctx.spi.with_dma(ctx.dma_channel);
358359

359-
unit.channel0.set_edge_signal(ctx.pcnt_source);
360+
unit.channel0
361+
.set_edge_signal(ctx.miso_input.peripheral_input());
360362
unit.channel0
361363
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
362364

@@ -396,7 +398,8 @@ mod tests {
396398
let unit = ctx.pcnt_unit;
397399
let mut spi = ctx.spi.with_dma(ctx.dma_channel);
398400

399-
unit.channel0.set_edge_signal(ctx.pcnt_source);
401+
unit.channel0
402+
.set_edge_signal(ctx.miso_input.peripheral_input());
400403
unit.channel0
401404
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
402405

@@ -500,7 +503,9 @@ mod tests {
500503
let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
501504
let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
502505

503-
ctx.pcnt_unit.channel0.set_edge_signal(ctx.pcnt_source);
506+
ctx.pcnt_unit
507+
.channel0
508+
.set_edge_signal(ctx.miso_input.peripheral_input());
504509
ctx.pcnt_unit
505510
.channel0
506511
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
@@ -601,7 +606,9 @@ mod tests {
601606
.with_buffers(dma_rx_buf, dma_tx_buf)
602607
.into_async();
603608

604-
ctx.pcnt_unit.channel0.set_edge_signal(ctx.pcnt_source);
609+
ctx.pcnt_unit
610+
.channel0
611+
.set_edge_signal(ctx.miso_input.peripheral_input());
605612
ctx.pcnt_unit
606613
.channel0
607614
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
@@ -635,7 +642,9 @@ mod tests {
635642
.with_buffers(dma_rx_buf, dma_tx_buf)
636643
.into_async();
637644

638-
ctx.pcnt_unit.channel0.set_edge_signal(ctx.pcnt_source);
645+
ctx.pcnt_unit
646+
.channel0
647+
.set_edge_signal(ctx.miso_input.peripheral_input());
639648
ctx.pcnt_unit
640649
.channel0
641650
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
@@ -701,9 +710,53 @@ mod tests {
701710
assert_eq!(&[0xff, 0xff, 0xff, 0xff], dma_rx_buf.as_slice());
702711
}
703712

713+
#[test]
714+
async fn cancel_stops_basic_async_spi_transfer(mut ctx: Context) {
715+
// Slow down. At 80kHz, the transfer is supposed to take a bit over 3 seconds.
716+
// We don't rely on the transfer speed much, just that it's slow enough
717+
// that we can detect pulses if cancelling the future leaves the transfer
718+
// running.
719+
ctx.spi
720+
.apply_config(&Config::default().with_frequency(Rate::from_khz(800)))
721+
.unwrap();
722+
723+
let mut spi = ctx.spi.into_async();
724+
725+
for i in 0..ctx.tx_buffer.len() {
726+
ctx.tx_buffer[i] = (i % 256) as u8;
727+
}
728+
729+
let transfer = spi.transfer_in_place_async(ctx.tx_buffer);
730+
731+
// Wait for a bit before cancelling
732+
let cancel = async {
733+
for _ in 0..100 {
734+
embassy_futures::yield_now().await;
735+
}
736+
};
737+
738+
embassy_futures::select::select(transfer, cancel).await;
739+
740+
// Listen for a while to see if the SPI peripheral correctly stopped.
741+
let detect_edge = ctx.miso_input.wait_for_any_edge();
742+
let wait = async {
743+
for _ in 0..10000 {
744+
embassy_futures::yield_now().await;
745+
}
746+
};
747+
748+
let result = embassy_futures::select::select(detect_edge, wait).await;
749+
750+
// Assert that we timed out - we should not have detected any edges
751+
assert!(
752+
matches!(result, embassy_futures::select::Either::Second(_)),
753+
"Detected edge after cancellation"
754+
);
755+
}
756+
704757
#[test]
705758
#[cfg(feature = "unstable")]
706-
fn cancel_stops_transaction(mut ctx: Context) {
759+
fn cancel_stops_dma_transaction(mut ctx: Context) {
707760
// Slow down. At 80kHz, the transfer is supposed to take a bit over 3 seconds.
708761
// This means that without working cancellation, the test case should
709762
// fail.

0 commit comments

Comments
 (0)