stm32/dma: refactor.

This commit is contained in:
Dario Nieuwenhuis
2023-04-17 00:04:54 +02:00
parent 46227bec1e
commit 173c65b543
14 changed files with 1025 additions and 1092 deletions

View File

@ -1,18 +1,31 @@
#![macro_use]
use core::future::Future;
use core::pin::Pin;
use core::sync::atomic::{fence, Ordering};
use core::task::Waker;
use core::task::{Context, Poll};
use embassy_cortex_m::interrupt::Priority;
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use super::{TransferOptions, Word, WordSize};
use super::{Dir, Word, WordSize};
use crate::_generated::BDMA_CHANNEL_COUNT;
use crate::dma::Request;
use crate::interrupt::{Interrupt, InterruptExt};
use crate::pac;
use crate::pac::bdma::vals;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct TransferOptions {}
impl Default for TransferOptions {
fn default() -> Self {
Self {}
}
}
impl From<WordSize> for vals::Size {
fn from(raw: WordSize) -> Self {
match raw {
@ -23,6 +36,15 @@ impl From<WordSize> for vals::Size {
}
}
impl From<Dir> for vals::Dir {
fn from(raw: Dir) -> Self {
match raw {
Dir::MemoryToPeripheral => Self::FROMMEMORY,
Dir::PeripheralToMemory => Self::FROMPERIPHERAL,
}
}
}
struct State {
ch_wakers: [AtomicWaker; BDMA_CHANNEL_COUNT],
}
@ -55,228 +77,267 @@ foreach_dma_channel! {
// BDMA1 in H7 doesn't use DMAMUX, which breaks
};
($channel_peri:ident, $dma_peri:ident, bdma, $channel_num:expr, $index:expr, $dmamux:tt) => {
impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri {
unsafe fn start_write<W: Word>(&mut self, _request: Request, buf: *const[W], reg_addr: *mut W, options: TransferOptions) {
let (ptr, len) = super::slice_ptr_parts(buf);
low_level_api::start_transfer(
pac::$dma_peri,
$channel_num,
#[cfg(any(bdma_v2, dmamux))]
_request,
vals::Dir::FROMMEMORY,
reg_addr as *const u32,
ptr as *mut u32,
len,
true,
vals::Size::from(W::bits()),
options,
#[cfg(dmamux)]
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
#[cfg(dmamux)]
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
);
impl sealed::Channel for crate::peripherals::$channel_peri {
fn regs(&self) -> pac::bdma::Dma {
pac::$dma_peri
}
unsafe fn start_write_repeated<W: Word>(&mut self, _request: Request, repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions) {
low_level_api::start_transfer(
pac::$dma_peri,
$channel_num,
#[cfg(any(bdma_v2, dmamux))]
_request,
vals::Dir::FROMMEMORY,
reg_addr as *const u32,
repeated as *mut u32,
count,
false,
vals::Size::from(W::bits()),
options,
#[cfg(dmamux)]
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
#[cfg(dmamux)]
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
)
fn num(&self) -> usize {
$channel_num
}
unsafe fn start_read<W: Word>(&mut self, _request: Request, reg_addr: *const W, buf: *mut [W], options: TransferOptions) {
let (ptr, len) = super::slice_ptr_parts_mut(buf);
low_level_api::start_transfer(
pac::$dma_peri,
$channel_num,
#[cfg(any(bdma_v2, dmamux))]
_request,
vals::Dir::FROMPERIPHERAL,
reg_addr as *const u32,
ptr as *mut u32,
len,
true,
vals::Size::from(W::bits()),
options,
#[cfg(dmamux)]
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
#[cfg(dmamux)]
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
);
fn index(&self) -> usize {
$index
}
unsafe fn start_double_buffered_read<W: super::Word>(
&mut self,
_request: Request,
_reg_addr: *const W,
_buffer0: *mut W,
_buffer1: *mut W,
_buffer_len: usize,
_options: TransferOptions,
) {
panic!("Unsafe double buffered mode is unavailable on BDMA");
}
unsafe fn set_buffer0<W: super::Word>(&mut self, _buffer: *mut W) {
panic!("Unsafe double buffered mode is unavailable on BDMA");
}
unsafe fn set_buffer1<W: super::Word>(&mut self, _buffer: *mut W) {
panic!("Unsafe double buffered mode is unavailable on BDMA");
}
unsafe fn is_buffer0_accessible(&mut self) -> bool {
panic!("Unsafe double buffered mode is unavailable on BDMA");
}
fn request_stop(&mut self){
unsafe {low_level_api::request_stop(pac::$dma_peri, $channel_num);}
}
fn is_running(&self) -> bool {
unsafe {low_level_api::is_running(pac::$dma_peri, $channel_num)}
}
fn remaining_transfers(&mut self) -> u16 {
unsafe {low_level_api::get_remaining_transfers(pac::$dma_peri, $channel_num)}
}
fn set_waker(&mut self, waker: &Waker) {
unsafe { low_level_api::set_waker($index, waker) }
}
fn on_irq() {
unsafe {
low_level_api::on_irq_inner(pac::$dma_peri, $channel_num, $index);
}
unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) }
}
}
impl crate::dma::Channel for crate::peripherals::$channel_peri {}
impl Channel for crate::peripherals::$channel_peri {}
};
}
mod low_level_api {
/// Safety: Must be called with a matching set of parameters for a valid dma channel
pub(crate) unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: usize, index: usize) {
let isr = dma.isr().read();
let cr = dma.ch(channel_num).cr();
if isr.teif(channel_num) {
panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, channel_num);
}
if isr.tcif(channel_num) && cr.read().tcie() {
cr.write(|_| ()); // Disable channel interrupts with the default value.
STATE.ch_wakers[index].wake();
}
}
#[cfg(any(bdma_v2, dmamux))]
pub type Request = u8;
#[cfg(not(any(bdma_v2, dmamux)))]
pub type Request = ();
#[cfg(dmamux)]
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static + super::dmamux::MuxChannel {}
#[cfg(not(dmamux))]
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static {}
pub(crate) mod sealed {
use super::*;
pub unsafe fn start_transfer(
dma: pac::bdma::Dma,
channel_number: u8,
#[cfg(any(bdma_v2, dmamux))] request: Request,
dir: vals::Dir,
pub trait Channel {
fn regs(&self) -> pac::bdma::Dma;
fn num(&self) -> usize;
fn index(&self) -> usize;
fn on_irq();
}
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Transfer<'a, C: Channel> {
channel: PeripheralRef<'a, C>,
}
impl<'a, C: Channel> Transfer<'a, C> {
pub unsafe fn new_read<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
peri_addr: *mut W,
buf: &'a mut [W],
options: TransferOptions,
) -> Self {
Self::new_read_raw(channel, request, peri_addr, buf, options)
}
pub unsafe fn new_read_raw<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
peri_addr: *mut W,
buf: *mut [W],
options: TransferOptions,
) -> Self {
into_ref!(channel);
let (ptr, len) = super::slice_ptr_parts_mut(buf);
assert!(len > 0 && len <= 0xFFFF);
Self::new_inner(
channel,
request,
Dir::PeripheralToMemory,
peri_addr as *const u32,
ptr as *mut u32,
len,
true,
W::bits(),
options,
)
}
pub unsafe fn new_write<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
buf: &'a [W],
peri_addr: *mut W,
options: TransferOptions,
) -> Self {
Self::new_write_raw(channel, request, buf, peri_addr, options)
}
pub unsafe fn new_write_raw<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
buf: *const [W],
peri_addr: *mut W,
options: TransferOptions,
) -> Self {
into_ref!(channel);
let (ptr, len) = super::slice_ptr_parts(buf);
assert!(len > 0 && len <= 0xFFFF);
Self::new_inner(
channel,
request,
Dir::MemoryToPeripheral,
peri_addr as *const u32,
ptr as *mut u32,
len,
true,
W::bits(),
options,
)
}
pub unsafe fn new_write_repeated<W: Word>(
channel: impl Peripheral<P = C> + 'a,
request: Request,
repeated: &'a W,
count: usize,
peri_addr: *mut W,
options: TransferOptions,
) -> Self {
into_ref!(channel);
Self::new_inner(
channel,
request,
Dir::MemoryToPeripheral,
peri_addr as *const u32,
repeated as *const W as *mut u32,
count,
false,
W::bits(),
options,
)
}
unsafe fn new_inner(
channel: PeripheralRef<'a, C>,
_request: Request,
dir: Dir,
peri_addr: *const u32,
mem_addr: *mut u32,
mem_len: usize,
incr_mem: bool,
data_size: vals::Size,
options: TransferOptions,
#[cfg(dmamux)] dmamux_regs: pac::dmamux::Dmamux,
#[cfg(dmamux)] dmamux_ch_num: u8,
) {
assert!(options.mburst == crate::dma::Burst::Single, "Burst mode not supported");
assert!(options.pburst == crate::dma::Burst::Single, "Burst mode not supported");
assert!(
options.flow_ctrl == crate::dma::FlowControl::Dma,
"Peripheral flow control not supported"
);
assert!(options.fifo_threshold.is_none(), "FIFO mode not supported");
let ch = dma.ch(channel_number as _);
reset_status(dma, channel_number);
#[cfg(dmamux)]
super::super::dmamux::configure_dmamux(dmamux_regs, dmamux_ch_num, request);
#[cfg(bdma_v2)]
critical_section::with(|_| dma.cselr().modify(|w| w.set_cs(channel_number as _, request)));
data_size: WordSize,
_options: TransferOptions,
) -> Self {
let ch = channel.regs().ch(channel.num());
// "Preceding reads and writes cannot be moved past subsequent writes."
fence(Ordering::SeqCst);
#[cfg(bdma_v2)]
critical_section::with(|_| channel.regs().cselr().modify(|w| w.set_cs(channel.num(), _request)));
let mut this = Self { channel };
this.clear_irqs();
#[cfg(dmamux)]
super::dmamux::configure_dmamux(&mut *this.channel, _request);
ch.par().write_value(peri_addr as u32);
ch.mar().write_value(mem_addr as u32);
ch.ndtr().write(|w| w.set_ndt(mem_len as u16));
ch.cr().write(|w| {
w.set_psize(data_size);
w.set_msize(data_size);
w.set_psize(data_size.into());
w.set_msize(data_size.into());
if incr_mem {
w.set_minc(vals::Inc::ENABLED);
} else {
w.set_minc(vals::Inc::DISABLED);
}
w.set_dir(dir);
w.set_dir(dir.into());
w.set_teie(true);
w.set_tcie(true);
w.set_en(true);
});
this
}
pub unsafe fn request_stop(dma: pac::bdma::Dma, channel_number: u8) {
reset_status(dma, channel_number);
let ch = dma.ch(channel_number as _);
// Disable the channel and interrupts with the default value.
ch.cr().write(|_| ());
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
fence(Ordering::SeqCst);
fn clear_irqs(&mut self) {
unsafe {
self.channel.regs().ifcr().write(|w| {
w.set_tcif(self.channel.num(), true);
w.set_teif(self.channel.num(), true);
})
}
}
pub unsafe fn is_running(dma: pac::bdma::Dma, ch: u8) -> bool {
let ch = dma.ch(ch as _);
ch.cr().read().en()
pub fn request_stop(&mut self) {
let ch = self.channel.regs().ch(self.channel.num());
// Disable the channel. Keep the IEs enabled so the irqs still fire.
unsafe {
ch.cr().write(|w| {
w.set_teie(true);
w.set_tcie(true);
})
}
}
pub fn is_running(&mut self) -> bool {
let ch = self.channel.regs().ch(self.channel.num());
unsafe { ch.cr().read() }.en()
}
/// Gets the total remaining transfers for the channel
/// Note: this will be zero for transfers that completed without cancellation.
pub unsafe fn get_remaining_transfers(dma: pac::bdma::Dma, ch: u8) -> u16 {
// get a handle on the channel itself
let ch = dma.ch(ch as _);
// read the remaining transfer count. If this is zero, the transfer completed fully.
ch.ndtr().read().ndt() as u16
pub fn get_remaining_transfers(&self) -> u16 {
let ch = self.channel.regs().ch(self.channel.num());
unsafe { ch.ndtr().read() }.ndt()
}
/// Sets the waker for the specified DMA channel
pub unsafe fn set_waker(state_number: usize, waker: &Waker) {
STATE.ch_wakers[state_number].register(waker);
pub fn blocking_wait(mut self) {
while self.is_running() {}
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
fence(Ordering::SeqCst);
core::mem::forget(self);
}
}
pub unsafe fn reset_status(dma: pac::bdma::Dma, channel_number: u8) {
dma.ifcr().write(|w| {
w.set_tcif(channel_number as _, true);
w.set_teif(channel_number as _, true);
});
impl<'a, C: Channel> Drop for Transfer<'a, C> {
fn drop(&mut self) {
self.request_stop();
while self.is_running() {}
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
fence(Ordering::SeqCst);
}
}
/// Safety: Must be called with a matching set of parameters for a valid dma channel
pub unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: u8, index: u8) {
let channel_num = channel_num as usize;
let index = index as usize;
impl<'a, C: Channel> Unpin for Transfer<'a, C> {}
impl<'a, C: Channel> Future for Transfer<'a, C> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
STATE.ch_wakers[self.channel.index()].register(cx.waker());
let isr = dma.isr().read();
let cr = dma.ch(channel_num).cr();
if isr.teif(channel_num) {
panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, channel_num);
}
if isr.tcif(channel_num) && cr.read().tcie() {
cr.write(|_| ()); // Disable channel interrupts with the default value.
STATE.ch_wakers[index].wake();
if self.is_running() {
Poll::Pending
} else {
Poll::Ready(())
}
}
}