esp-hal/hil-test/tests/embassy_interrupt_spi_dma.rs
Dániel Buga 392d5ccdc1
Unstabilize CPU-related functionality, merge modules (#3099)
* Move Cpu to a new module

* Move cpu_control's contents into cpu

* Merge raw_core functions

* Hide cpu control APIs

* Move reset into cpu

* Fix software_reset_cpu

* Allow inlining cpu functions

* Changelog

* Mark software_reset no-return

* Move WakeupReason

* Remove PeripheralIter

* Move cpu into system

* Mark system stable
2025-02-07 14:15:26 +00:00

336 lines
11 KiB
Rust

//! Reproduction and regression test for a sneaky issue.
//% CHIPS: esp32 esp32s2 esp32s3 esp32c3 esp32c6 esp32h2
//% FEATURES: unstable embassy
//% ENV(single_integrated): ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE = single-integrated
//% ENV(multiple_integrated): ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE = multiple-integrated
//% ENV(generic_queue): ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE = generic
//% ENV(generic_queue): ESP_HAL_EMBASSY_CONFIG_GENERIC_QUEUE_SIZE = 16
#![no_std]
#![no_main]
use embassy_time::{Duration, Instant, Timer};
use esp_hal::{
dma::{DmaRxBuf, DmaTxBuf},
dma_buffers,
interrupt::{software::SoftwareInterruptControl, Priority},
peripheral::Peripheral,
spi::{
master::{Config, Spi},
Mode,
},
time::Rate,
timer::AnyTimer,
Blocking,
};
use esp_hal_embassy::InterruptExecutor;
use hil_test as _;
use portable_atomic::AtomicBool;
macro_rules! mk_static {
($t:ty,$val:expr) => {{
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
#[deny(unused_attributes)]
let x = STATIC_CELL.uninit().write(($val));
x
}};
}
static STOP_INTERRUPT_TASK: AtomicBool = AtomicBool::new(false);
static INTERRUPT_TASK_WORKING: AtomicBool = AtomicBool::new(false);
#[cfg(any(esp32, esp32s2, esp32s3))]
#[embassy_executor::task]
async fn interrupt_driven_task(spi: esp_hal::spi::master::SpiDma<'static, Blocking>) {
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(128);
let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
let mut spi = spi.with_buffers(dma_rx_buf, dma_tx_buf).into_async();
loop {
let mut buffer: [u8; 8] = [0; 8];
INTERRUPT_TASK_WORKING.store(true, portable_atomic::Ordering::Relaxed);
spi.transfer_in_place_async(&mut buffer).await.unwrap();
INTERRUPT_TASK_WORKING.store(false, portable_atomic::Ordering::Relaxed);
if STOP_INTERRUPT_TASK.load(portable_atomic::Ordering::Relaxed) {
break;
}
Timer::after(Duration::from_millis(1)).await;
}
}
#[cfg(not(any(esp32, esp32s2, esp32s3)))]
#[embassy_executor::task]
async fn interrupt_driven_task(i2s_tx: esp_hal::i2s::master::I2s<'static, Blocking>) {
let mut i2s_tx = i2s_tx.into_async().i2s_tx.build();
loop {
let mut buffer: [u8; 8] = [0; 8];
INTERRUPT_TASK_WORKING.store(true, portable_atomic::Ordering::Relaxed);
i2s_tx.write_dma_async(&mut buffer).await.unwrap();
INTERRUPT_TASK_WORKING.store(false, portable_atomic::Ordering::Relaxed);
if STOP_INTERRUPT_TASK.load(portable_atomic::Ordering::Relaxed) {
break;
}
Timer::after(Duration::from_millis(1)).await;
}
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod test {
use super::*;
#[test]
async fn dma_does_not_lock_up_when_used_in_different_executors() {
let peripherals = esp_hal::init(esp_hal::Config::default());
cfg_if::cfg_if! {
if #[cfg(systimer)] {
use esp_hal::timer::systimer::SystemTimer;
let systimer = SystemTimer::new(peripherals.SYSTIMER);
esp_hal_embassy::init([
AnyTimer::from(systimer.alarm0),
AnyTimer::from(systimer.alarm1),
]);
} else {
use esp_hal::timer::timg::TimerGroup;
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_hal_embassy::init([
AnyTimer::from(timg0.timer0),
AnyTimer::from(timg0.timer1),
]);
}
}
cfg_if::cfg_if! {
if #[cfg(any(feature = "esp32", feature = "esp32s2"))] {
let dma_channel1 = peripherals.DMA_SPI2;
let dma_channel2 = peripherals.DMA_SPI3;
} else {
let dma_channel1 = peripherals.DMA_CH0;
let dma_channel2 = peripherals.DMA_CH1;
}
}
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(1024);
let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
let (_, mosi) = hil_test::common_test_pins!(peripherals);
let mut spi = Spi::new(
peripherals.SPI2,
Config::default()
.with_frequency(Rate::from_khz(10000))
.with_mode(Mode::_0),
)
.unwrap()
.with_miso(unsafe { mosi.clone_unchecked() })
.with_mosi(mosi)
.with_dma(dma_channel1)
.with_buffers(dma_rx_buf, dma_tx_buf)
.into_async();
#[cfg(any(esp32, esp32s2, esp32s3))]
let other_peripheral = Spi::new(
peripherals.SPI3,
Config::default()
.with_frequency(Rate::from_khz(10000))
.with_mode(Mode::_0),
)
.unwrap()
.with_dma(dma_channel2);
#[cfg(not(any(esp32, esp32s2, esp32s3)))]
let other_peripheral = {
let (_, rx_descriptors, _, tx_descriptors) = dma_buffers!(128);
esp_hal::i2s::master::I2s::new(
peripherals.I2S0,
esp_hal::i2s::master::Standard::Philips,
esp_hal::i2s::master::DataFormat::Data8Channel8,
Rate::from_khz(8),
dma_channel2,
rx_descriptors,
tx_descriptors,
)
};
let sw_ints = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
let interrupt_executor = mk_static!(
InterruptExecutor<1>,
InterruptExecutor::new(sw_ints.software_interrupt1)
);
let spawner = interrupt_executor.start(Priority::Priority3);
spawner
.spawn(interrupt_driven_task(other_peripheral))
.unwrap();
let start = Instant::now();
let mut buffer: [u8; 1024] = [0; 1024];
let mut dst_buffer: [u8; 1024] = [0; 1024];
let mut i = 0;
loop {
buffer.fill(i);
dst_buffer.fill(i.wrapping_add(1));
spi.transfer_async(&mut dst_buffer, &buffer).await.unwrap();
// make sure the transfer didn't end prematurely
assert!(dst_buffer.iter().all(|&v| v == i));
if start.elapsed() > Duration::from_secs(1) {
break;
}
i = i.wrapping_add(1);
}
// make sure the other peripheral didn't get stuck
STOP_INTERRUPT_TASK.store(true, portable_atomic::Ordering::Relaxed);
while INTERRUPT_TASK_WORKING.load(portable_atomic::Ordering::Relaxed) {}
}
// Reproducer of https://github.com/esp-rs/esp-hal/issues/2369
#[cfg(multi_core)]
#[test]
async fn dma_does_not_lock_up_on_core_1() {
use embassy_time::Timer;
use esp_hal::peripherals::SPI2;
use portable_atomic::{AtomicU32, Ordering};
cfg_if::cfg_if! {
if #[cfg(pdma)] {
use esp_hal::dma::Spi2DmaChannel as DmaChannel;
} else {
type DmaChannel = esp_hal::dma::DmaChannel0;
}
}
const BUFFER_SIZE: usize = 256;
static LOOP_COUNT: AtomicU32 = AtomicU32::new(0);
pub struct SpiPeripherals {
pub spi: SPI2,
pub dma_channel: DmaChannel,
}
#[embassy_executor::task]
async fn run_spi(peripherals: SpiPeripherals) {
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(3200);
let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
let mut spi = Spi::new(
peripherals.spi,
Config::default()
.with_frequency(Rate::from_khz(100))
.with_mode(Mode::_0),
)
.unwrap()
.with_dma(peripherals.dma_channel)
.with_buffers(dma_rx_buf, dma_tx_buf)
.into_async();
let send_buffer = mk_static!([u8; BUFFER_SIZE], [0u8; BUFFER_SIZE]);
loop {
let mut buffer = [0; 8];
embedded_hal_async::spi::SpiBus::transfer(&mut spi, &mut buffer, send_buffer)
.await
.unwrap();
LOOP_COUNT.fetch_add(1, Ordering::Relaxed);
}
}
let peripherals = esp_hal::init(esp_hal::Config::default());
cfg_if::cfg_if! {
if #[cfg(systimer)] {
use esp_hal::timer::systimer::SystemTimer;
let systimer = SystemTimer::new(peripherals.SYSTIMER);
esp_hal_embassy::init([
AnyTimer::from(systimer.alarm0),
AnyTimer::from(systimer.alarm1),
]);
} else {
use esp_hal::timer::timg::TimerGroup;
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_hal_embassy::init([
AnyTimer::from(timg0.timer0),
AnyTimer::from(timg0.timer1),
]);
}
}
cfg_if::cfg_if! {
if #[cfg(pdma)] {
let dma_channel = peripherals.DMA_SPI2;
} else {
let dma_channel = peripherals.DMA_CH0;
}
}
let spi_peripherals = SpiPeripherals {
spi: peripherals.SPI2,
dma_channel,
};
let cpu1_fnctn = {
move || {
use esp_hal::interrupt::Priority;
use esp_hal_embassy::InterruptExecutor;
let sw_ints = esp_hal::interrupt::software::SoftwareInterruptControl::new(
peripherals.SW_INTERRUPT,
);
let software_interrupt = sw_ints.software_interrupt2;
let hp_executor = mk_static!(
InterruptExecutor<2>,
InterruptExecutor::new(software_interrupt)
);
let high_pri_spawner = hp_executor.start(Priority::Priority2);
// hub75 runs as high priority task
high_pri_spawner.spawn(run_spi(spi_peripherals)).ok();
// This loop is necessary to avoid parking the core after creating the interrupt
// executor.
loop {}
}
};
use esp_hal::system::{CpuControl, Stack};
const DISPLAY_STACK_SIZE: usize = 8192;
let app_core_stack = mk_static!(Stack<DISPLAY_STACK_SIZE>, Stack::new());
let cpu_control = CpuControl::new(peripherals.CPU_CTRL);
let mut _cpu_control = cpu_control;
#[allow(static_mut_refs)]
let _guard = _cpu_control
.start_app_core(app_core_stack, cpu1_fnctn)
.unwrap();
let mut last = 0u32;
for _ in 0..5 {
Timer::after(Duration::from_millis(200)).await;
let next = LOOP_COUNT.load(Ordering::Relaxed);
assert_ne!(next, last, "stuck");
last = next;
}
// make sure the other peripheral didn't get stuck
STOP_INTERRUPT_TASK.store(true, portable_atomic::Ordering::Relaxed);
while INTERRUPT_TASK_WORKING.load(portable_atomic::Ordering::Relaxed) {}
}
}