STM32 DMA: Use interrupt flags instead of atomics

This commit is contained in:
Timo Kröger 2021-07-22 00:12:06 +02:00 committed by Dario Nieuwenhuis
parent 9c503a9256
commit 5a4a5ce334

View File

@ -1,7 +1,6 @@
use core::future::Future; use core::future::Future;
use core::task::Poll; use core::task::Poll;
use atomic_polyfill::{AtomicU8, Ordering};
use embassy::interrupt::{Interrupt, InterruptExt}; use embassy::interrupt::{Interrupt, InterruptExt};
use embassy::util::{AtomicWaker, OnDrop}; use embassy::util::{AtomicWaker, OnDrop};
use futures::future::poll_fn; use futures::future::poll_fn;
@ -14,22 +13,16 @@ use crate::rcc::sealed::RccPeripheral;
use super::{Channel, Request}; use super::{Channel, Request};
const CH_COUNT: usize = pac::peripheral_count!(DMA) * 8; const CH_COUNT: usize = pac::peripheral_count!(DMA) * 8;
const CH_STATUS_NONE: u8 = 0;
const CH_STATUS_COMPLETED: u8 = 1;
const CH_STATUS_ERROR: u8 = 2;
struct State { struct State {
ch_wakers: [AtomicWaker; CH_COUNT], ch_wakers: [AtomicWaker; CH_COUNT],
ch_status: [AtomicU8; CH_COUNT],
} }
impl State { impl State {
const fn new() -> Self { const fn new() -> Self {
const AW: AtomicWaker = AtomicWaker::new(); const AW: AtomicWaker = AtomicWaker::new();
const AU: AtomicU8 = AtomicU8::new(CH_STATUS_NONE);
Self { Self {
ch_wakers: [AW; CH_COUNT], ch_wakers: [AW; CH_COUNT],
ch_status: [AU; CH_COUNT],
} }
} }
} }
@ -56,25 +49,21 @@ pub(crate) unsafe fn do_transfer(
assert!(mem_len <= 0xFFFF); assert!(mem_len <= 0xFFFF);
// Reset status // Reset status
// Generate a DMB here to flush the store buffer (M7) before enabling the DMA let isrn = channel_number as usize / 4;
STATE.ch_status[state_number as usize].store(CH_STATUS_NONE, Ordering::Release); let isrbit = channel_number as usize % 4;
dma.ifcr(isrn).write(|w| {
w.set_tcif(isrbit, true);
w.set_teif(isrbit, true);
});
let ch = dma.st(channel_number as _); let ch = dma.st(channel_number as _);
let on_drop = OnDrop::new(move || unsafe { let on_drop = OnDrop::new(move || unsafe {
ch.cr().modify(|w| { // Disable the channel and interrupts with the default value.
w.set_tcie(false); ch.cr().write(|_| ());
w.set_teie(false);
w.set_en(false);
});
while ch.cr().read().en() {}
// Disabling the DMA mid transfer might cause some flags to be set, clear them all for the // Wait for the transfer to complete when it was ongoing.
// next transfer while ch.cr().read().en() {}
dma.ifcr(channel_number as usize / 4).write(|w| {
w.set_tcif(channel_number as usize % 4, true);
w.set_teif(channel_number as usize % 4, true);
});
}); });
#[cfg(dmamux)] #[cfg(dmamux)]
@ -110,15 +99,20 @@ pub(crate) unsafe fn do_transfer(
let res = poll_fn(|cx| { let res = poll_fn(|cx| {
let n = state_number as usize; let n = state_number as usize;
STATE.ch_wakers[n].register(cx.waker()); STATE.ch_wakers[n].register(cx.waker());
match STATE.ch_status[n].load(Ordering::Acquire) {
CH_STATUS_NONE => Poll::Pending, let isr = dma.isr(isrn).read();
x => Poll::Ready(x),
// TODO handle error
assert!(!isr.teif(isrbit));
if isr.tcif(isrbit) {
Poll::Ready(())
} else {
Poll::Pending
} }
}) })
.await; .await;
// TODO handle error
assert!(res == CH_STATUS_COMPLETED);
drop(on_drop) drop(on_drop)
} }
} }
@ -137,16 +131,13 @@ unsafe fn on_irq() {
(dma, $dma:ident) => { (dma, $dma:ident) => {
for isrn in 0..2 { for isrn in 0..2 {
let isr = pac::$dma.isr(isrn).read(); let isr = pac::$dma.isr(isrn).read();
pac::$dma.ifcr(isrn).write_value(isr);
let dman = dma_num!($dma);
for chn in 0..4 { for chn in 0..4 {
let n = dman * 8 + isrn * 4 + chn; let cr = pac::$dma.st(isrn * 4 + chn).cr();
if isr.teif(chn) {
STATE.ch_status[n].store(CH_STATUS_ERROR, Ordering::Relaxed); if isr.tcif(chn) && cr.read().tcie() {
STATE.ch_wakers[n].wake(); cr.write(|_| ()); // Disable channel interrupts with the default value.
} else if isr.tcif(chn) { let n = dma_num!($dma) * 8 + isrn * 4 + chn;
STATE.ch_status[n].store(CH_STATUS_COMPLETED, Ordering::Relaxed);
STATE.ch_wakers[n].wake(); STATE.ch_wakers[n].wake();
} }
} }