From cf5b1ea9f593d1d80b718b88330f041b59d071f1 Mon Sep 17 00:00:00 2001 From: elagil Date: Mon, 25 Aug 2025 21:10:59 +0200 Subject: feat: gpdma support (wip) --- embassy-stm32/src/cryp/mod.rs | 3 - embassy-stm32/src/dma/gpdma/linked_list.rs | 77 ++--- embassy-stm32/src/dma/gpdma/mod.rs | 91 ++++-- embassy-stm32/src/dma/gpdma/ringbuffer.rs | 283 ------------------ embassy-stm32/src/dma/gpdma/ringbuffered.rs | 433 ++++++++++++++++++++++++++++ embassy-stm32/src/dma/mod.rs | 2 + embassy-stm32/src/dma/ringbuffer/mod.rs | 20 ++ embassy-stm32/src/spdifrx/mod.rs | 4 - embassy-stm32/src/usart/mod.rs | 2 - 9 files changed, 561 insertions(+), 354 deletions(-) delete mode 100644 embassy-stm32/src/dma/gpdma/ringbuffer.rs create mode 100644 embassy-stm32/src/dma/gpdma/ringbuffered.rs diff --git a/embassy-stm32/src/cryp/mod.rs b/embassy-stm32/src/cryp/mod.rs index 35d9f8cce..0173b2b5d 100644 --- a/embassy-stm32/src/cryp/mod.rs +++ b/embassy-stm32/src/cryp/mod.rs @@ -1814,7 +1814,6 @@ impl<'d, T: Instance> Cryp<'d, T, Async> { // Configure DMA to transfer input to crypto core. let dst_ptr: *mut u32 = T::regs().din().as_ptr(); let options = TransferOptions { - #[cfg(not(gpdma))] priority: crate::dma::Priority::High, ..Default::default() }; @@ -1834,7 +1833,6 @@ impl<'d, T: Instance> Cryp<'d, T, Async> { // Configure DMA to transfer input to crypto core. let dst_ptr: *mut u32 = T::regs().din().as_ptr(); let options = TransferOptions { - #[cfg(not(gpdma))] priority: crate::dma::Priority::High, ..Default::default() }; @@ -1853,7 +1851,6 @@ impl<'d, T: Instance> Cryp<'d, T, Async> { // Configure DMA to get output from crypto core. let src_ptr = T::regs().dout().as_ptr(); let options = TransferOptions { - #[cfg(not(gpdma))] priority: crate::dma::Priority::VeryHigh, ..Default::default() }; diff --git a/embassy-stm32/src/dma/gpdma/linked_list.rs b/embassy-stm32/src/dma/gpdma/linked_list.rs index b24b2e7eb..7de9a1441 100644 --- a/embassy-stm32/src/dma/gpdma/linked_list.rs +++ b/embassy-stm32/src/dma/gpdma/linked_list.rs @@ -8,10 +8,6 @@ use crate::dma::{ word::{Word, WordSize}, Dir, Request, }; -use core::{ - ptr, - sync::atomic::{AtomicUsize, Ordering}, -}; /// The mode in which to run the linked list. #[derive(Debug)] @@ -28,6 +24,7 @@ pub enum RunMode { /// /// Also works for 2D-capable GPDMA channels, but does not use 2D capabilities. #[derive(Debug, Copy, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] pub struct LinearItem { /// Transfer register 1. @@ -146,7 +143,9 @@ impl LinearItem { llr.set_usa(true); llr.set_uda(true); llr.set_ull(true); - llr.set_la(next); + + // Lower two bits are ignored: 32 bit aligned. + llr.set_la(next >> 2); self.llr = llr.0; } @@ -159,78 +158,82 @@ impl LinearItem { } } +/// A table of linked list items. +#[repr(C)] pub struct Table { - current_index: AtomicUsize, - items: [LinearItem; ITEM_COUNT], + /// The items. + pub items: [LinearItem; ITEM_COUNT], } impl Table { /// Create a new table. - pub fn new(items: [LinearItem; ITEM_COUNT], run_mode: RunMode) -> Self { + pub fn new(items: [LinearItem; ITEM_COUNT]) -> Self { assert!(!items.is_empty()); - let mut this = Self { - current_index: AtomicUsize::new(0), - items, - }; + Self { items } + } + pub fn link(&mut self, run_mode: RunMode) { if matches!(run_mode, RunMode::Once | RunMode::Repeat) { - this.link_sequential(); + self.link_sequential(); } if matches!(run_mode, RunMode::Repeat) { - this.link_repeat(); + self.link_repeat(); } - - this } + /// The number of linked list items.s pub fn len(&self) -> usize { self.items.len() } - /// Items are linked together sequentially. + /// Link items of given indices together: first -> second. + pub fn link_indices(&mut self, first: usize, second: usize) { + assert!(first < self.len()); + assert!(second < self.len()); + + let second_item = self.offset_address(second); + self.items[first].link_to(second_item); + } + + /// Link items sequentially. pub fn link_sequential(&mut self) { - if self.items.len() > 1 { + if self.len() > 1 { for index in 0..(self.items.len() - 1) { - let next = ptr::addr_of!(self.items[index + 1]) as u16; + let next = self.offset_address(index + 1); self.items[index].link_to(next); } } } - /// Last item links to first item. + /// Link last to first item. pub fn link_repeat(&mut self) { - let first_item = self.items.first().unwrap(); - let first_address = ptr::addr_of!(first_item) as u16; + let first_address = self.offset_address(0); self.items.last_mut().unwrap().link_to(first_address); } - /// The index of the next item. - pub fn next_index(&self) -> usize { - let mut next_index = self.current_index.load(Ordering::Relaxed) + 1; - if next_index >= self.len() { - next_index = 0; + /// Unlink all items. + pub fn unlink(&mut self) { + for item in self.items.iter_mut() { + item.unlink(); } - - next_index - } - - /// Unlink the next item. - pub fn unlink_next(&mut self) { - let next_index = self.next_index(); - self.items[next_index].unlink(); } /// Linked list base address (upper 16 address bits). pub fn base_address(&self) -> u16 { - ((ptr::addr_of!(self.items) as u32) >> 16) as _ + ((&raw const self.items as u32) >> 16) as _ } /// Linked list offset address (lower 16 address bits) at the selected index. pub fn offset_address(&self, index: usize) -> u16 { assert!(self.items.len() > index); - (ptr::addr_of!(self.items[index]) as u32) as _ + let address = &raw const self.items[index] as _; + + // Ensure 32 bit address alignment. + assert_eq!(address & 0b11, 0); + + address } } diff --git a/embassy-stm32/src/dma/gpdma/mod.rs b/embassy-stm32/src/dma/gpdma/mod.rs index 07acd2cf0..f65048d1f 100644 --- a/embassy-stm32/src/dma/gpdma/mod.rs +++ b/embassy-stm32/src/dma/gpdma/mod.rs @@ -8,7 +8,6 @@ use core::task::{Context, Poll}; use embassy_hal_internal::Peri; use embassy_sync::waitqueue::AtomicWaker; use linked_list::Table; -use stm32_metapac::gpdma::regs; use super::word::{Word, WordSize}; use super::{AnyChannel, Channel, Dir, Request, STATE}; @@ -16,8 +15,8 @@ use crate::interrupt::typelevel::Interrupt; use crate::pac; use crate::pac::gpdma::vals; -mod linked_list; -mod ringbuffer; +pub mod linked_list; +pub mod ringbuffered; pub(crate) struct ChannelInfo { pub(crate) dma: pac::gpdma::Gpdma, @@ -56,9 +55,12 @@ impl From for pac::gpdma::vals::Prio { #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub struct TransferOptions { - priority: Priority, - half_transfer_ir: bool, - complete_transfer_ir: bool, + /// Request priority level. + pub priority: Priority, + /// Enable half transfer interrupt. + pub half_transfer_ir: bool, + /// Enable transfer complete interrupt. + pub complete_transfer_ir: bool, } impl Default for TransferOptions { @@ -81,6 +83,17 @@ impl From for vals::Dw { } } +impl From for WordSize { + fn from(raw: vals::Dw) -> Self { + match raw { + vals::Dw::BYTE => Self::OneByte, + vals::Dw::HALF_WORD => Self::TwoBytes, + vals::Dw::WORD => Self::FourBytes, + _ => panic!("Invalid word size"), + } + } +} + pub(crate) struct ChannelState { waker: AtomicWaker, complete_count: AtomicUsize, @@ -94,7 +107,7 @@ impl ChannelState { } /// safety: must be called only once -pub(crate) unsafe fn init(cs: critical_section::CriticalSection, irq_priority: Priority) { +pub(crate) unsafe fn init(cs: critical_section::CriticalSection, irq_priority: crate::interrupt::Priority) { foreach_interrupt! { ($peri:ident, gpdma, $block:ident, $signal_name:ident, $irq:ident) => { crate::interrupt::typelevel::$irq::set_priority_with_cs(cs, irq_priority); @@ -142,24 +155,30 @@ impl AnyChannel { ); } + if sr.htf() { + ch.fcr().write(|w| w.set_htf(true)); + } + if sr.tcf() { + ch.fcr().write(|w| w.set_tcf(true)); state.complete_count.fetch_add(1, Ordering::Release); } - if sr.suspf() || sr.tcf() { + if sr.suspf() { // disable all xxIEs to prevent the irq from firing again. ch.cr().write(|_| {}); // Wake the future. It'll look at tcf and see it's set. - state.waker.wake(); } + state.waker.wake(); } fn get_remaining_transfers(&self) -> u16 { let info = self.info(); let ch = info.dma.ch(info.num); + let word_size: WordSize = ch.tr1().read().ddw().into(); - ch.br1().read().bndt() + ch.br1().read().bndt() / word_size.bytes() as u16 } unsafe fn configure( @@ -238,21 +257,23 @@ impl AnyChannel { ch.cr().write(|w| w.set_reset(true)); ch.fcr().write(|w| w.0 = 0xFFFF_FFFF); // clear all irqs - ch.lbar().write(|reg| reg.set_lba(table.base_address())); - // Enable all linked-list field updates. - let mut llr = regs::ChLlr(0); - llr.set_ut1(true); - llr.set_ut2(true); - llr.set_ub1(true); - llr.set_usa(true); - llr.set_uda(true); - llr.set_ull(true); - - llr.set_la(table.offset_address(0)); + // Empty LLI0. + ch.br1().write(|w| w.set_bndt(0)); - ch.llr().write(|_| llr.0); + // Enable all linked-list field updates. + ch.llr().write(|w| { + w.set_ut1(true); + w.set_ut2(true); + w.set_ub1(true); + w.set_usa(true); + w.set_uda(true); + w.set_ull(true); + + // Lower two bits are ignored: 32 bit aligned. + w.set_la(table.offset_address(0) >> 2); + }); ch.tr3().write(|_| {}); // no address offsets. @@ -281,12 +302,23 @@ impl AnyChannel { ch.cr().modify(|w| w.set_susp(true)) } + fn request_pause(&self) { + let info = self.info(); + let ch = info.dma.ch(info.num); + + // Disable the channel without overwriting the existing configuration + ch.cr().modify(|w| { + w.set_en(false); + }); + } + fn is_running(&self) -> bool { let info = self.info(); let ch = info.dma.ch(info.num); let sr = ch.sr().read(); - !sr.tcf() && !sr.suspf() + + !sr.tcf() && !sr.suspf() && !sr.idlef() } fn poll_stop(&self) -> Poll<()> { @@ -305,7 +337,6 @@ impl AnyChannel { #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct LinkedListTransfer<'a, const ITEM_COUNT: usize> { channel: PeripheralRef<'a, AnyChannel>, - table: Table, } impl<'a, const ITEM_COUNT: usize> LinkedListTransfer<'a, ITEM_COUNT> { @@ -328,7 +359,7 @@ impl<'a, const ITEM_COUNT: usize> LinkedListTransfer<'a, ITEM_COUNT> { channel.configure_linked_list(&table, options); channel.start(); - Self { channel, table } + Self { channel } } /// Request the transfer to stop. @@ -515,12 +546,22 @@ impl<'a> Transfer<'a> { } /// Request the transfer to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. /// /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. pub fn request_stop(&mut self) { self.channel.request_stop() } + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + /// Return whether this transfer is still running. /// /// If this returns `false`, it can be because either the transfer finished, or diff --git a/embassy-stm32/src/dma/gpdma/ringbuffer.rs b/embassy-stm32/src/dma/gpdma/ringbuffer.rs deleted file mode 100644 index c327e811e..000000000 --- a/embassy-stm32/src/dma/gpdma/ringbuffer.rs +++ /dev/null @@ -1,283 +0,0 @@ -//! GPDMA ring buffer implementation. -//! -//! FIXME: add request_pause functionality? -use core::{ - sync::atomic::{fence, Ordering}, - task::Waker, -}; - -use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; - -use crate::dma::{ - gpdma::linked_list::{LinearItem, RunMode, Table}, - ringbuffer::{DmaCtrl, Error, ReadableDmaRingBuffer, WritableDmaRingBuffer}, - word::Word, - Channel, Dir, Request, -}; - -use super::{AnyChannel, TransferOptions, STATE}; - -struct DmaCtrlImpl<'a>(PeripheralRef<'a, AnyChannel>); - -impl<'a> DmaCtrl for DmaCtrlImpl<'a> { - fn get_remaining_transfers(&self) -> usize { - self.0.get_remaining_transfers() as _ - } - - fn reset_complete_count(&mut self) -> usize { - let state = &STATE[self.0.id as usize]; - - return state.complete_count.swap(0, Ordering::AcqRel); - } - - fn set_waker(&mut self, waker: &Waker) { - STATE[self.0.id as usize].waker.register(waker); - } -} - -/// Ringbuffer for receiving data using GPDMA linked-list mode. -pub struct ReadableRingBuffer<'a, W: Word> { - channel: PeripheralRef<'a, AnyChannel>, - ringbuf: ReadableDmaRingBuffer<'a, W>, - table: Table<2>, -} - -impl<'a, W: Word> ReadableRingBuffer<'a, W> { - /// Create a new ring buffer. - pub unsafe fn new( - channel: impl Peripheral

+ 'a, - request: Request, - peri_addr: *mut W, - buffer: &'a mut [W], - mut options: TransferOptions, - ) -> Self { - into_ref!(channel); - let channel: PeripheralRef<'a, AnyChannel> = channel.map_into(); - - let half_len = buffer.len() / 2; - assert_eq!(half_len * 2, buffer.len()); - - options.half_transfer_ir = false; - options.complete_transfer_ir = true; - - let items = [ - LinearItem::new_read(request, peri_addr, &mut buffer[..half_len], options), - LinearItem::new_read(request, peri_addr, &mut buffer[half_len..], options), - ]; - - let table = Table::new(items, RunMode::Once); - - let this = Self { - channel, - ringbuf: ReadableDmaRingBuffer::new(buffer), - table, - }; - - this.channel.configure_linked_list(&this.table, options); - - this - } - - /// Start the ring buffer operation. - /// - /// You must call this after creating it for it to work. - pub fn start(&mut self) { - self.channel.start(); - } - - /// Clear all data in the ring buffer. - pub fn clear(&mut self) { - self.ringbuf.reset(&mut DmaCtrlImpl(self.channel.reborrow())); - } - - /// Read elements from the ring buffer - /// Return a tuple of the length read and the length remaining in the buffer - /// If not all of the elements were read, then there will be some elements in the buffer remaining - /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read - /// Error is returned if the portion to be read was overwritten by the DMA controller. - pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), Error> { - self.ringbuf.read(&mut DmaCtrlImpl(self.channel.reborrow()), buf) - } - - /// Read an exact number of elements from the ringbuffer. - /// - /// Returns the remaining number of elements available for immediate reading. - /// Error is returned if the portion to be read was overwritten by the DMA controller. - /// - /// Async/Wake Behavior: - /// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point, - /// and when it wraps around. This means that when called with a buffer of length 'M', when this - /// ring buffer was created with a buffer of size 'N': - /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. - /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. - pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result { - self.ringbuf - .read_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) - .await - } - - /// The current length of the ringbuffer - pub fn len(&mut self) -> Result { - Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?) - } - - /// The capacity of the ringbuffer - pub const fn capacity(&self) -> usize { - self.ringbuf.cap() - } - - /// Set a waker to be woken when at least one byte is received. - pub fn set_waker(&mut self, waker: &Waker) { - DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); - } - - /// Request the DMA to stop. - /// The configuration for this channel will **not be preserved**. If you need to restart the transfer - /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. - /// - /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. - pub fn request_stop(&mut self) { - self.channel.request_stop() - } - - /// Return whether DMA is still running. - /// - /// If this returns `false`, it can be because either the transfer finished, or - /// it was requested to stop early with [`request_stop`](Self::request_stop). - pub fn is_running(&mut self) -> bool { - self.channel.is_running() - } -} - -impl<'a, W: Word> Drop for ReadableRingBuffer<'a, W> { - 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); - } -} - -/// Ringbuffer for writing data using DMA circular mode. -pub struct WritableRingBuffer<'a, W: Word> { - channel: PeripheralRef<'a, AnyChannel>, - ringbuf: WritableDmaRingBuffer<'a, W>, -} - -impl<'a, W: Word> WritableRingBuffer<'a, W> { - /// Create a new ring buffer. - pub unsafe fn new( - channel: impl Peripheral

+ 'a, - _request: Request, - peri_addr: *mut W, - buffer: &'a mut [W], - mut options: TransferOptions, - ) -> Self { - into_ref!(channel); - let channel: PeripheralRef<'a, AnyChannel> = channel.map_into(); - - let len = buffer.len(); - let dir = Dir::MemoryToPeripheral; - let data_size = W::size(); - let buffer_ptr = buffer.as_mut_ptr(); - - options.half_transfer_ir = true; - options.complete_transfer_ir = true; - - channel.configure( - _request, - dir, - peri_addr as *mut u32, - buffer_ptr as *mut u32, - len, - true, - data_size, - data_size, - options, - ); - - Self { - channel, - ringbuf: WritableDmaRingBuffer::new(buffer), - } - } - - /// Start the ring buffer operation. - /// - /// You must call this after creating it for it to work. - pub fn start(&mut self) { - self.channel.start(); - } - - /// Clear all data in the ring buffer. - pub fn clear(&mut self) { - self.ringbuf.reset(&mut DmaCtrlImpl(self.channel.reborrow())); - } - - /// Write elements directly to the raw buffer. - /// This can be used to fill the buffer before starting the DMA transfer. - pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), Error> { - self.ringbuf.write_immediate(buf) - } - - /// Write elements from the ring buffer - /// Return a tuple of the length written and the length remaining in the buffer - pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), Error> { - self.ringbuf.write(&mut DmaCtrlImpl(self.channel.reborrow()), buf) - } - - /// Write an exact number of elements to the ringbuffer. - pub async fn write_exact(&mut self, buffer: &[W]) -> Result { - self.ringbuf - .write_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) - .await - } - - /// Wait for any ring buffer write error. - pub async fn wait_write_error(&mut self) -> Result { - self.ringbuf - .wait_write_error(&mut DmaCtrlImpl(self.channel.reborrow())) - .await - } - - /// The current length of the ringbuffer - pub fn len(&mut self) -> Result { - Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?) - } - - /// The capacity of the ringbuffer - pub const fn capacity(&self) -> usize { - self.ringbuf.cap() - } - - /// Set a waker to be woken when at least one byte is received. - pub fn set_waker(&mut self, waker: &Waker) { - DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); - } - - /// Request the DMA to stop. - /// - /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. - pub fn request_stop(&mut self) { - self.channel.request_stop() - } - - /// Return whether DMA is still running. - /// - /// If this returns `false`, it can be because either the transfer finished, or - /// it was requested to stop early with [`request_stop`](Self::request_stop). - pub fn is_running(&mut self) -> bool { - self.channel.is_running() - } -} - -impl<'a, W: Word> Drop for WritableRingBuffer<'a, W> { - 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); - } -} diff --git a/embassy-stm32/src/dma/gpdma/ringbuffered.rs b/embassy-stm32/src/dma/gpdma/ringbuffered.rs new file mode 100644 index 000000000..fd0a98e23 --- /dev/null +++ b/embassy-stm32/src/dma/gpdma/ringbuffered.rs @@ -0,0 +1,433 @@ +//! GPDMA ring buffer implementation. +//! +//! FIXME: add request_pause functionality? +use core::{ + future::poll_fn, + sync::atomic::{fence, Ordering}, + task::Waker, +}; + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; + +use crate::dma::{ + gpdma::linked_list::{LinearItem, RunMode, Table}, + ringbuffer::{DmaCtrl, Error, ReadableDmaRingBuffer, WritableDmaRingBuffer}, + word::Word, + Channel, Request, +}; + +use super::{AnyChannel, TransferOptions, STATE}; + +struct DmaCtrlImpl<'a>(PeripheralRef<'a, AnyChannel>); + +impl<'a> DmaCtrl for DmaCtrlImpl<'a> { + fn get_remaining_transfers(&self) -> usize { + self.0.get_remaining_transfers() as _ + } + + fn reset_complete_count(&mut self) -> usize { + let state = &STATE[self.0.id as usize]; + + state.complete_count.swap(0, Ordering::AcqRel) + } + + fn set_waker(&mut self, waker: &Waker) { + STATE[self.0.id as usize].waker.register(waker); + } +} + +/// The current buffer half (e.g. for DMA or the user application). +#[derive(Debug, PartialEq, PartialOrd)] +enum BufferHalf { + First, + Second, +} + +impl BufferHalf { + fn toggle(&mut self) { + *self = match *self { + Self::First => Self::Second, + Self::Second => Self::First, + }; + } +} + +/// Ringbuffer for receiving data using GPDMA linked-list mode. +pub struct ReadableRingBuffer<'a, W: Word> { + channel: PeripheralRef<'a, AnyChannel>, + ringbuf: ReadableDmaRingBuffer<'a, W>, + table: Table<1>, + user_buffer_half: BufferHalf, +} + +impl<'a, W: Word> ReadableRingBuffer<'a, W> { + /// Create a new ring buffer. + pub unsafe fn new( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buffer: &'a mut [W], + mut options: TransferOptions, + ) -> Self { + into_ref!(channel); + let channel: PeripheralRef<'a, AnyChannel> = channel.map_into(); + + let half_len = buffer.len() / 2; + assert_eq!(half_len * 2, buffer.len()); + + options.half_transfer_ir = true; + options.complete_transfer_ir = true; + + // let items = [ + // LinearItem::new_read(request, peri_addr, &mut buffer[..half_len], options), + // LinearItem::new_read(request, peri_addr, &mut buffer[half_len..], options), + // ]; + let items = [LinearItem::new_read(request, peri_addr, buffer, options)]; + + let table = Table::new(items); + + let this = Self { + channel, + ringbuf: ReadableDmaRingBuffer::new(buffer), + table, + user_buffer_half: BufferHalf::First, + }; + + this.channel.configure_linked_list(&this.table, options); + + this + } + + /// Start the ring buffer operation. + /// + /// You must call this after creating it for it to work. + pub fn start(&mut self) { + self.channel.start(); + } + + /// Clear all data in the ring buffer. + pub fn clear(&mut self) { + self.ringbuf.reset(&mut DmaCtrlImpl(self.channel.reborrow())); + } + + /// Read elements from the ring buffer + /// Return a tuple of the length read and the length remaining in the buffer + /// If not all of the elements were read, then there will be some elements in the buffer remaining + /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read + /// Error is returned if the portion to be read was overwritten by the DMA controller. + pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), Error> { + self.ringbuf.read(&mut DmaCtrlImpl(self.channel.reborrow()), buf) + } + + /// Read an exact number of elements from the ringbuffer. + /// + /// Returns the remaining number of elements available for immediate reading. + /// Error is returned if the portion to be read was overwritten by the DMA controller. + /// + /// Async/Wake Behavior: + /// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point, + /// and when it wraps around. This means that when called with a buffer of length 'M', when this + /// ring buffer was created with a buffer of size 'N': + /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. + /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. + pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result { + self.ringbuf + .read_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) + .await + } + + /// The current length of the ringbuffer + pub fn len(&mut self) -> Result { + Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?) + } + + /// The capacity of the ringbuffer + pub const fn capacity(&self) -> usize { + self.ringbuf.cap() + } + + /// Set a waker to be woken when at least one byte is received. + pub fn set_waker(&mut self, waker: &Waker) { + DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); + } + + /// Request the DMA to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_stop(&mut self) { + self.channel.request_stop() + } + + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + + /// Return whether DMA is still running. + /// + /// If this returns `false`, it can be because either the transfer finished, or + /// it was requested to stop early with [`request_stop`](Self::request_stop). + pub fn is_running(&mut self) -> bool { + self.channel.is_running() + } + + /// Stop the DMA transfer and await until the buffer is full. + /// + /// This disables the DMA transfer's circular mode so that the transfer + /// stops when the buffer is full. + /// + /// This is designed to be used with streaming input data such as the + /// I2S/SAI or ADC. + /// + /// When using the UART, you probably want `request_stop()`. + pub async fn stop(&mut self) { + // wait until cr.susp reads as true + poll_fn(|cx| { + self.set_waker(cx.waker()); + self.channel.poll_stop() + }) + .await + } +} + +impl<'a, W: Word> Drop for ReadableRingBuffer<'a, W> { + 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); + } +} + +/// Ringbuffer for writing data using DMA circular mode. +pub struct WritableRingBuffer<'a, W: Word> { + channel: PeripheralRef<'a, AnyChannel>, + ringbuf: WritableDmaRingBuffer<'a, W>, + table: Table<1>, + user_buffer_half: BufferHalf, +} + +impl<'a, W: Word> WritableRingBuffer<'a, W> { + /// Create a new ring buffer. + pub unsafe fn new( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buffer: &'a mut [W], + mut options: TransferOptions, + ) -> Self { + into_ref!(channel); + let channel: PeripheralRef<'a, AnyChannel> = channel.map_into(); + + let half_len = buffer.len() / 2; + assert_eq!(half_len * 2, buffer.len()); + + options.half_transfer_ir = true; + options.complete_transfer_ir = true; + + // let items = [ + // LinearItem::new_write(request, &mut buffer[..half_len], peri_addr, options), + // LinearItem::new_write(request, &mut buffer[half_len..], peri_addr, options), + // ]; + let items = [LinearItem::new_write(request, buffer, peri_addr, options)]; + let table = Table::new(items); + + let this = Self { + channel, + ringbuf: WritableDmaRingBuffer::new(buffer), + table, + user_buffer_half: BufferHalf::First, + }; + + this + } + + fn dma_buffer_half(&self) -> BufferHalf { + if self.ringbuf.read_index(0) < self.ringbuf.cap() / 2 { + BufferHalf::First + } else { + BufferHalf::Second + } + } + + fn link_next_buffer(&mut self) { + self.table.unlink(); + + match self.user_buffer_half { + BufferHalf::First => self.table.link_indices(0, 1), + BufferHalf::Second => self.table.link_indices(1, 0), + } + + self.user_buffer_half.toggle(); + } + + /// Start the ring buffer operation. + /// + /// You must call this after creating it for it to work. + pub fn start(&mut self) { + unsafe { + self.channel.configure_linked_list( + &self.table, + TransferOptions { + half_transfer_ir: true, + complete_transfer_ir: true, + ..Default::default() + }, + ) + }; + self.table.link(RunMode::Repeat); + self.channel.start(); + } + + /// Clear all data in the ring buffer. + pub fn clear(&mut self) { + self.ringbuf.reset(&mut DmaCtrlImpl(self.channel.reborrow())); + } + + /// Write elements directly to the raw buffer. + /// This can be used to fill the buffer before starting the DMA transfer. + pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), Error> { + self.ringbuf.write_immediate(buf) + } + + /// Write elements from the ring buffer + /// Return a tuple of the length written and the length remaining in the buffer + pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), Error> { + self.ringbuf.write(&mut DmaCtrlImpl(self.channel.reborrow()), buf) + } + + /// Write an exact number of elements to the ringbuffer. + pub async fn write_exact(&mut self, buffer: &[W]) -> Result { + return self + .ringbuf + .write_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) + .await; + + let mut remaining = buffer.len(); + + let mut remaining_cap = 0; + let cap = self.ringbuf.cap(); + + while remaining > 0 { + let dma_buffer_half = self.dma_buffer_half(); + if dma_buffer_half == self.user_buffer_half { + self.link_next_buffer(); + } + + let write_index = self.ringbuf.write_index(0); + let len = match dma_buffer_half { + BufferHalf::First => { + // if write_index < cap / 2 { + // error!("write index: {}", write_index); + // panic!() + // } + info!("Write second"); + + // Fill up second buffer half when DMA reads the first. + cap - write_index + } + BufferHalf::Second => { + // if write_index >= cap / 2 { + // error!("write index: {}", write_index); + // panic!() + // } + info!("Write first"); + + // Fill up first buffer half when DMA reads the second. + cap / 2 - write_index + } + } + .min(remaining); + + remaining_cap = self + .ringbuf + .write_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) + .await?; + + remaining -= len; + } + + Ok(remaining_cap) + } + + /// Wait for any ring buffer write error. + pub async fn wait_write_error(&mut self) -> Result { + self.ringbuf + .wait_write_error(&mut DmaCtrlImpl(self.channel.reborrow())) + .await + } + + /// The current length of the ringbuffer + pub fn len(&mut self) -> Result { + Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?) + } + + /// The capacity of the ringbuffer + pub const fn capacity(&self) -> usize { + self.ringbuf.cap() + } + + /// Set a waker to be woken when at least one byte is received. + pub fn set_waker(&mut self, waker: &Waker) { + DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); + } + + /// Request the DMA to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_stop(&mut self) { + self.channel.request_stop() + } + + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + + /// Return whether DMA is still running. + /// + /// If this returns `false`, it can be because either the transfer finished, or + /// it was requested to stop early with [`request_stop`](Self::request_stop). + pub fn is_running(&mut self) -> bool { + self.channel.is_running() + } + + /// Stop the DMA transfer and await until the buffer is full. + /// + /// This disables the DMA transfer's circular mode so that the transfer + /// stops when the buffer is full. + /// + /// This is designed to be used with streaming input data such as the + /// I2S/SAI or ADC. + /// + /// When using the UART, you probably want `request_stop()`. + pub async fn stop(&mut self) { + // wait until cr.susp reads as true + poll_fn(|cx| { + self.set_waker(cx.waker()); + self.channel.poll_stop() + }) + .await + } +} + +impl<'a, W: Word> Drop for WritableRingBuffer<'a, W> { + 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); + } +} diff --git a/embassy-stm32/src/dma/mod.rs b/embassy-stm32/src/dma/mod.rs index d3b070a6d..030f906d2 100644 --- a/embassy-stm32/src/dma/mod.rs +++ b/embassy-stm32/src/dma/mod.rs @@ -9,6 +9,8 @@ pub use dma_bdma::*; #[cfg(gpdma)] pub(crate) mod gpdma; #[cfg(gpdma)] +pub use gpdma::ringbuffered::*; +#[cfg(gpdma)] pub use gpdma::*; #[cfg(dmamux)] diff --git a/embassy-stm32/src/dma/ringbuffer/mod.rs b/embassy-stm32/src/dma/ringbuffer/mod.rs index e462c71d4..99960bc74 100644 --- a/embassy-stm32/src/dma/ringbuffer/mod.rs +++ b/embassy-stm32/src/dma/ringbuffer/mod.rs @@ -92,6 +92,16 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { } } + /// The current ring-buffer read index. + pub fn read_index(&self, offset: usize) -> usize { + self.read_index.as_index(self.cap(), offset) + } + + /// The current ring-buffer write index. + pub fn write_index(&self, offset: usize) -> usize { + self.write_index.as_index(self.cap(), offset) + } + /// Reset the ring buffer to its initial state. pub fn reset(&mut self, dma: &mut impl DmaCtrl) { dma.reset_complete_count(); @@ -208,6 +218,16 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { } } + /// The current ring-buffer read index. + pub fn read_index(&self, offset: usize) -> usize { + self.read_index.as_index(self.cap(), offset) + } + + /// The current ring-buffer write index. + pub fn write_index(&self, offset: usize) -> usize { + self.write_index.as_index(self.cap(), offset) + } + /// Reset the ring buffer to its initial state. The buffer after the reset will be full. pub fn reset(&mut self, dma: &mut impl DmaCtrl) { dma.reset_complete_count(); diff --git a/embassy-stm32/src/spdifrx/mod.rs b/embassy-stm32/src/spdifrx/mod.rs index 9c42217f0..d3b4a0b10 100644 --- a/embassy-stm32/src/spdifrx/mod.rs +++ b/embassy-stm32/src/spdifrx/mod.rs @@ -8,7 +8,6 @@ use embassy_sync::waitqueue::AtomicWaker; use crate::dma::ringbuffer::Error as RingbufferError; pub use crate::dma::word; -#[cfg(not(gpdma))] use crate::dma::ReadableRingBuffer; use crate::dma::{Channel, TransferOptions}; use crate::gpio::{AfType, AnyPin, Pull, SealedPin as _}; @@ -58,7 +57,6 @@ macro_rules! impl_spdifrx_pin { /// Ring-buffered SPDIFRX driver. /// /// Data is read by DMAs and stored in a ring buffer. -#[cfg(not(gpdma))] pub struct Spdifrx<'d, T: Instance> { _peri: Peri<'d, T>, spdifrx_in: Option>, @@ -118,7 +116,6 @@ impl Default for Config { } } -#[cfg(not(gpdma))] impl<'d, T: Instance> Spdifrx<'d, T> { fn dma_opts() -> TransferOptions { TransferOptions { @@ -236,7 +233,6 @@ impl<'d, T: Instance> Spdifrx<'d, T> { } } -#[cfg(not(gpdma))] impl<'d, T: Instance> Drop for Spdifrx<'d, T> { fn drop(&mut self) { T::info().regs.cr().modify(|cr| cr.set_spdifen(0x00)); diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 5bece6d66..3d95de897 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -1965,9 +1965,7 @@ pub use buffered::*; pub use crate::usart::buffered::InterruptHandler as BufferedInterruptHandler; mod buffered; -#[cfg(not(gpdma))] mod ringbuffered; -#[cfg(not(gpdma))] pub use ringbuffered::RingBufferedUartRx; #[cfg(any(usart_v1, usart_v2))] -- cgit