diff --git a/esp-hal/src/dma/buffers.rs b/esp-hal/src/dma/buffers.rs new file mode 100644 index 000000000..e783645ce --- /dev/null +++ b/esp-hal/src/dma/buffers.rs @@ -0,0 +1,915 @@ +use core::ptr::null_mut; + +use super::*; +use crate::soc::is_slice_in_dram; +#[cfg(esp32s3)] +use crate::soc::is_slice_in_psram; + +/// Holds all the information needed to configure a DMA channel for a transfer. +pub struct Preparation { + pub(super) start: *mut DmaDescriptor, + /// block size for PSRAM transfers (TODO: enable burst mode for non external + /// memory?) + #[cfg_attr(not(esp32s3), allow(dead_code))] + pub(super) block_size: Option, + // burst_mode, alignment, check_owner, etc. +} + +/// [DmaTxBuffer] is a DMA descriptor + memory combo that can be used for +/// transmitting data from a DMA channel to a peripheral's FIFO. +/// +/// # Safety +/// +/// The implementing type must keep all its descriptors and the buffers they +/// point to valid while the buffer is being transferred. +pub unsafe trait DmaTxBuffer { + /// A type providing operations that are safe to perform on the buffer + /// whilst the DMA is actively using it. + type View; + + /// Prepares the buffer for an imminent transfer and returns + /// information required to use this buffer. + /// + /// Note: This operation is idempotent. + fn prepare(&mut self) -> Preparation; + + /// This is called before the DMA starts using the buffer. + fn into_view(self) -> Self::View; + + /// This is called after the DMA is done using the buffer. + fn from_view(view: Self::View) -> Self; + + /// Returns the maximum number of bytes that would be transmitted by this + /// buffer. + /// + /// This is a convenience hint for SPI. Most peripherals don't care how long + /// the transfer is. + fn length(&self) -> usize; +} + +/// [DmaRxBuffer] is a DMA descriptor + memory combo that can be used for +/// receiving data from a peripheral's FIFO to a DMA channel. +/// +/// Note: Implementations of this trait may only support having a single EOF bit +/// which resides in the last descriptor. There will be a separate trait in +/// future to support multiple EOFs. +/// +/// # Safety +/// +/// The implementing type must keep all its descriptors and the buffers they +/// point to valid while the buffer is being transferred. +pub unsafe trait DmaRxBuffer { + /// A type providing operations that are safe to perform on the buffer + /// whilst the DMA is actively using it. + type View; + + /// Prepares the buffer for an imminent transfer and returns + /// information required to use this buffer. + /// + /// Note: This operation is idempotent. + fn prepare(&mut self) -> Preparation; + + /// This is called before the DMA starts using the buffer. + fn into_view(self) -> Self::View; + + /// This is called after the DMA is done using the buffer. + fn from_view(view: Self::View) -> Self; + + /// Returns the maximum number of bytes that can be received by this buffer. + /// + /// This is a convenience hint for SPI. Most peripherals don't care how long + /// the transfer is. + fn length(&self) -> usize; +} + +/// An in-progress view into [DmaRxBuf]/[DmaTxBuf]. +/// +/// In the future, this could support peeking into state of the +/// descriptors/buffers. +pub struct BufView(T); + +/// Error returned from Dma[Rx|Tx|RxTx]Buf operations. +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DmaBufError { + /// More descriptors are needed for the buffer size + InsufficientDescriptors, + /// Descriptors or buffers are not located in a supported memory region + UnsupportedMemoryRegion, + /// Buffer is not aligned to the required size + InvalidAlignment, + /// Invalid chunk size: must be > 0 and <= 4095 + InvalidChunkSize, +} + +/// DMA buffer alignments +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DmaBufBlkSize { + /// 16 bytes + Size16 = 16, + /// 32 bytes + Size32 = 32, + /// 64 bytes + Size64 = 64, +} + +/// DMA transmit buffer +/// +/// This is a contiguous buffer linked together by DMA descriptors of length +/// 4095 at most. It can only be used for transmitting data to a peripheral's +/// FIFO. See [DmaRxBuf] for receiving data. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DmaTxBuf { + descriptors: DescriptorSet<'static>, + buffer: &'static mut [u8], + block_size: Option, +} + +impl DmaTxBuf { + /// Creates a new [DmaTxBuf] from some descriptors and a buffer. + /// + /// There must be enough descriptors for the provided buffer. + /// Each descriptor can handle 4092 bytes worth of buffer. + /// + /// Both the descriptors and buffer must be in DMA-capable memory. + /// Only DRAM is supported for descriptors. + pub fn new( + descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + ) -> Result { + Self::new_with_block_size(descriptors, buffer, None) + } + + /// Compute max chunk size based on block size + pub const fn compute_chunk_size(block_size: Option) -> usize { + max_chunk_size(block_size) + } + + /// Compute the number of descriptors required for a given block size and + /// buffer size + pub const fn compute_descriptor_count( + buffer_size: usize, + block_size: Option, + ) -> usize { + descriptor_count(buffer_size, Self::compute_chunk_size(block_size), false) + } + + /// Creates a new [DmaTxBuf] from some descriptors and a buffer. + /// + /// There must be enough descriptors for the provided buffer. + /// Each descriptor can handle at most 4095 bytes worth of buffer. + /// Optionally, a block size can be provided for PSRAM & Burst transfers. + /// + /// Both the descriptors and buffer must be in DMA-capable memory. + /// Only DRAM is supported for descriptors. + pub fn new_with_block_size( + descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + block_size: Option, + ) -> Result { + cfg_if::cfg_if! { + if #[cfg(esp32s3)] { + // buffer can be either DRAM or PSRAM (if supported) + if !is_slice_in_dram(buffer) && !is_slice_in_psram(buffer) { + return Err(DmaBufError::UnsupportedMemoryRegion); + } + // if its PSRAM, the block_size/alignment must be specified + if is_slice_in_psram(buffer) && block_size.is_none() { + return Err(DmaBufError::InvalidAlignment); + } + } else { + #[cfg(any(esp32,esp32s2))] + if buffer.len() % 4 != 0 && buffer.as_ptr() as usize % 4 != 0 { + // ESP32 requires word alignment for DMA buffers. + // ESP32-S2 technically supports byte-aligned DMA buffers, but the + // transfer ends up writing out of bounds if the buffer's length + // is 2 or 3 (mod 4). + return Err(DmaBufError::InvalidAlignment); + } + // buffer can only be DRAM + if !is_slice_in_dram(buffer) { + return Err(DmaBufError::UnsupportedMemoryRegion); + } + } + } + + let block_size = if is_slice_in_dram(buffer) { + // no need for block size if the buffer is in DRAM + None + } else { + block_size + }; + let mut buf = Self { + descriptors: DescriptorSet::new(descriptors)?, + buffer, + block_size, + }; + + buf.descriptors + .link_with_buffer(buf.buffer, max_chunk_size(block_size))?; + buf.set_length(buf.capacity()); + + Ok(buf) + } + + /// Consume the buf, returning the descriptors and buffer. + pub fn split(self) -> (&'static mut [DmaDescriptor], &'static mut [u8]) { + (self.descriptors.into_inner(), self.buffer) + } + + /// Returns the size of the underlying buffer + pub fn capacity(&self) -> usize { + self.buffer.len() + } + + /// Return the number of bytes that would be transmitted by this buf. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.descriptors + .linked_iter() + .map(|d| d.len()) + .sum::() + } + + /// Reset the descriptors to only transmit `len` amount of bytes from this + /// buf. + /// + /// The number of bytes in data must be less than or equal to the buffer + /// size. + pub fn set_length(&mut self, len: usize) { + assert!(len <= self.buffer.len()); + + unwrap!(self + .descriptors + .set_tx_length(len, max_chunk_size(self.block_size))); + } + + /// Fills the TX buffer with the bytes provided in `data` and reset the + /// descriptors to only cover the filled section. + /// + /// The number of bytes in data must be less than or equal to the buffer + /// size. + pub fn fill(&mut self, data: &[u8]) { + self.set_length(data.len()); + self.as_mut_slice()[..data.len()].copy_from_slice(data); + } + + /// Returns the buf as a mutable slice than can be written. + pub fn as_mut_slice(&mut self) -> &mut [u8] { + self.buffer + } + + /// Returns the buf as a slice than can be read. + pub fn as_slice(&self) -> &[u8] { + self.buffer + } +} + +unsafe impl DmaTxBuffer for DmaTxBuf { + type View = BufView; + + fn prepare(&mut self) -> Preparation { + for desc in self.descriptors.linked_iter_mut() { + // In non-circular mode, we only set `suc_eof` for the last descriptor to signal + // the end of the transfer. + desc.reset_for_tx(desc.next.is_null()); + } + + #[cfg(esp32s3)] + if crate::soc::is_valid_psram_address(self.buffer.as_ptr() as usize) { + unsafe { + crate::soc::cache_writeback_addr( + self.buffer.as_ptr() as u32, + self.buffer.len() as u32, + ) + }; + } + + Preparation { + start: self.descriptors.head(), + block_size: self.block_size, + } + } + + fn into_view(self) -> BufView { + BufView(self) + } + + fn from_view(view: Self::View) -> Self { + view.0 + } + + fn length(&self) -> usize { + self.len() + } +} + +/// DMA receive buffer +/// +/// This is a contiguous buffer linked together by DMA descriptors of length +/// 4092. It can only be used for receiving data from a peripheral's FIFO. +/// See [DmaTxBuf] for transmitting data. +pub struct DmaRxBuf { + descriptors: DescriptorSet<'static>, + buffer: &'static mut [u8], +} + +impl DmaRxBuf { + /// Creates a new [DmaRxBuf] from some descriptors and a buffer. + /// + /// There must be enough descriptors for the provided buffer. + /// Each descriptor can handle 4092 bytes worth of buffer. + /// + /// Both the descriptors and buffer must be in DMA-capable memory. + /// Only DRAM is supported. + pub fn new( + descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + ) -> Result { + if !is_slice_in_dram(buffer) { + return Err(DmaBufError::UnsupportedMemoryRegion); + } + + let mut buf = Self { + descriptors: DescriptorSet::new(descriptors)?, + buffer, + }; + + buf.descriptors + .link_with_buffer(buf.buffer, max_chunk_size(None))?; + buf.set_length(buf.capacity()); + + Ok(buf) + } + + /// Consume the buf, returning the descriptors and buffer. + pub fn split(self) -> (&'static mut [DmaDescriptor], &'static mut [u8]) { + (self.descriptors.into_inner(), self.buffer) + } + + /// Returns the size of the underlying buffer + pub fn capacity(&self) -> usize { + self.buffer.len() + } + + /// Returns the maximum number of bytes that this buf has been configured to + /// receive. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.descriptors + .linked_iter() + .map(|d| d.size()) + .sum::() + } + + /// Reset the descriptors to only receive `len` amount of bytes into this + /// buf. + /// + /// The number of bytes in data must be less than or equal to the buffer + /// size. + pub fn set_length(&mut self, len: usize) { + assert!(len <= self.buffer.len()); + + unwrap!(self.descriptors.set_rx_length(len, max_chunk_size(None))); + } + + /// Returns the entire underlying buffer as a slice than can be read. + pub fn as_slice(&self) -> &[u8] { + self.buffer + } + + /// Returns the entire underlying buffer as a slice than can be written. + pub fn as_mut_slice(&mut self) -> &mut [u8] { + self.buffer + } + + /// Return the number of bytes that was received by this buf. + pub fn number_of_received_bytes(&self) -> usize { + self.descriptors + .linked_iter() + .map(|d| d.len()) + .sum::() + } + + /// Reads the received data into the provided `buf`. + /// + /// If `buf.len()` is less than the amount of received data then only the + /// first `buf.len()` bytes of received data is written into `buf`. + /// + /// Returns the number of bytes in written to `buf`. + pub fn read_received_data(&self, mut buf: &mut [u8]) -> usize { + let capacity = buf.len(); + for chunk in self.received_data() { + if buf.is_empty() { + break; + } + let to_fill; + (to_fill, buf) = buf.split_at_mut(chunk.len()); + to_fill.copy_from_slice(chunk); + } + + capacity - buf.len() + } + + /// Returns the received data as an iterator of slices. + pub fn received_data(&self) -> impl Iterator { + self.descriptors.linked_iter().map(|desc| { + // SAFETY: We set up the descriptor to point to a subslice of the buffer, and + // here we are only recreating that slice with a perhaps shorter length. + // We are also not accessing `self.buffer` while this slice is alive, so we + // are not violating any aliasing rules. + unsafe { core::slice::from_raw_parts(desc.buffer.cast_const(), desc.len()) } + }) + } +} + +unsafe impl DmaRxBuffer for DmaRxBuf { + type View = BufView; + + fn prepare(&mut self) -> Preparation { + for desc in self.descriptors.linked_iter_mut() { + desc.reset_for_rx(); + } + + Preparation { + start: self.descriptors.head(), + block_size: None, + } + } + + fn into_view(self) -> BufView { + BufView(self) + } + + fn from_view(view: Self::View) -> Self { + view.0 + } + + fn length(&self) -> usize { + self.len() + } +} + +/// DMA transmit and receive buffer. +/// +/// This is a (single) contiguous buffer linked together by two sets of DMA +/// descriptors of length 4092 each. +/// It can be used for simultaneously transmitting to and receiving from a +/// peripheral's FIFO. These are typically full-duplex transfers. +pub struct DmaRxTxBuf { + rx_descriptors: DescriptorSet<'static>, + tx_descriptors: DescriptorSet<'static>, + buffer: &'static mut [u8], +} + +impl DmaRxTxBuf { + /// Creates a new [DmaRxTxBuf] from some descriptors and a buffer. + /// + /// There must be enough descriptors for the provided buffer. + /// Each descriptor can handle 4092 bytes worth of buffer. + /// + /// Both the descriptors and buffer must be in DMA-capable memory. + /// Only DRAM is supported. + pub fn new( + rx_descriptors: &'static mut [DmaDescriptor], + tx_descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + ) -> Result { + if !is_slice_in_dram(buffer) { + return Err(DmaBufError::UnsupportedMemoryRegion); + } + + let mut buf = Self { + rx_descriptors: DescriptorSet::new(rx_descriptors)?, + tx_descriptors: DescriptorSet::new(tx_descriptors)?, + buffer, + }; + buf.rx_descriptors + .link_with_buffer(buf.buffer, max_chunk_size(None))?; + buf.tx_descriptors + .link_with_buffer(buf.buffer, max_chunk_size(None))?; + buf.set_length(buf.capacity()); + + Ok(buf) + } + + /// Consume the buf, returning the rx descriptors, tx descriptors and + /// buffer. + pub fn split( + self, + ) -> ( + &'static mut [DmaDescriptor], + &'static mut [DmaDescriptor], + &'static mut [u8], + ) { + ( + self.rx_descriptors.into_inner(), + self.tx_descriptors.into_inner(), + self.buffer, + ) + } + + /// Return the size of the underlying buffer. + pub fn capacity(&self) -> usize { + self.buffer.len() + } + + /// Return the number of bytes that would be transmitted by this buf. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.tx_descriptors + .linked_iter() + .map(|d| d.len()) + .sum::() + } + + /// Returns the entire buf as a slice than can be read. + pub fn as_slice(&self) -> &[u8] { + self.buffer + } + + /// Returns the entire buf as a slice than can be written. + pub fn as_mut_slice(&mut self) -> &mut [u8] { + self.buffer + } + + /// Reset the descriptors to only transmit/receive `len` amount of bytes + /// with this buf. + /// + /// `len` must be less than or equal to the buffer size. + pub fn set_length(&mut self, len: usize) { + assert!(len <= self.buffer.len()); + + unwrap!(self.rx_descriptors.set_rx_length(len, max_chunk_size(None))); + unwrap!(self.tx_descriptors.set_tx_length(len, max_chunk_size(None))); + } +} + +unsafe impl DmaTxBuffer for DmaRxTxBuf { + type View = BufView; + + fn prepare(&mut self) -> Preparation { + for desc in self.tx_descriptors.linked_iter_mut() { + // In non-circular mode, we only set `suc_eof` for the last descriptor to signal + // the end of the transfer. + desc.reset_for_tx(desc.next.is_null()); + } + + Preparation { + start: self.tx_descriptors.head(), + block_size: None, // TODO: support block size! + } + } + + fn into_view(self) -> BufView { + BufView(self) + } + + fn from_view(view: Self::View) -> Self { + view.0 + } + + fn length(&self) -> usize { + self.len() + } +} + +unsafe impl DmaRxBuffer for DmaRxTxBuf { + type View = BufView; + + fn prepare(&mut self) -> Preparation { + for desc in self.rx_descriptors.linked_iter_mut() { + desc.reset_for_rx(); + } + + Preparation { + start: self.rx_descriptors.head(), + block_size: None, // TODO: support block size! + } + } + + fn into_view(self) -> BufView { + BufView(self) + } + + fn from_view(view: Self::View) -> Self { + view.0 + } + + fn length(&self) -> usize { + self.len() + } +} + +/// DMA Streaming Receive Buffer. +/// +/// This is a contiguous buffer linked together by DMA descriptors, and the +/// buffer is evenly distributed between each descriptor provided. +/// +/// It is used for continuously streaming data from a peripheral's FIFO. +/// +/// It does so by maintaining sliding window of descriptors that progresses when +/// you call [DmaRxStreamBufView::consume]. +/// +/// The list starts out like so `A (empty) -> B (empty) -> C (empty) -> D +/// (empty) -> NULL`. +/// +/// As the DMA writes to the buffers the list progresses like so: +/// - `A (empty) -> B (empty) -> C (empty) -> D (empty) -> NULL` +/// - `A (full) -> B (empty) -> C (empty) -> D (empty) -> NULL` +/// - `A (full) -> B (full) -> C (empty) -> D (empty) -> NULL` +/// - `A (full) -> B (full) -> C (full) -> D (empty) -> NULL` +/// +/// As you call [DmaRxStreamBufView::consume] the list (approximately) +/// progresses like so: +/// - `A (full) -> B (full) -> C (full) -> D (empty) -> NULL` +/// - `B (full) -> C (full) -> D (empty) -> A (empty) -> NULL` +/// - `C (full) -> D (empty) -> A (empty) -> B (empty) -> NULL` +/// - `D (empty) -> A (empty) -> B (empty) -> C (empty) -> NULL` +/// +/// If all the descriptors fill up, the [DmaRxInterrupt::DescriptorEmpty] +/// interrupt will fire and the DMA will stop writing, at which point it is up +/// to you to resume/restart the transfer. +/// +/// Note: This buffer will not tell you when this condition occurs, you should +/// check with the driver to see if the DMA has stopped. +/// +/// When constructing this buffer, it is important to tune the ratio between the +/// chunk size and buffer size appropriately. Smaller chunk sizes means you +/// receive data more frequently but this means the DMA interrupts +/// ([DmaRxInterrupt::Done]) also fire more frequently (if you use them). +/// +/// See [DmaRxStreamBufView] for APIs available whilst a transfer is in +/// progress. +pub struct DmaRxStreamBuf { + descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], +} + +impl DmaRxStreamBuf { + /// Creates a new [DmaRxStreamBuf] evenly distributing the buffer between + /// the provided descriptors. + pub fn new( + descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + ) -> Result { + if !is_slice_in_dram(descriptors) { + return Err(DmaBufError::UnsupportedMemoryRegion); + } + if !is_slice_in_dram(buffer) { + return Err(DmaBufError::UnsupportedMemoryRegion); + } + + if descriptors.is_empty() { + return Err(DmaBufError::InsufficientDescriptors); + } + + // Evenly distribute the buffer between the descriptors. + let chunk_size = buffer.len() / descriptors.len(); + + if chunk_size > 4095 { + return Err(DmaBufError::InsufficientDescriptors); + } + + // Check that the last descriptor can hold the excess + let excess = buffer.len() % descriptors.len(); + if chunk_size + excess > 4095 { + return Err(DmaBufError::InsufficientDescriptors); + } + + // Link up all the descriptors (but not in a circle). + let mut next = null_mut(); + for desc in descriptors.iter_mut().rev() { + desc.next = next; + next = desc; + } + + let mut chunks = buffer.chunks_exact_mut(chunk_size); + for (desc, chunk) in descriptors.iter_mut().zip(chunks.by_ref()) { + desc.buffer = chunk.as_mut_ptr(); + desc.set_size(chunk.len()); + } + + let remainder = chunks.into_remainder(); + debug_assert_eq!(remainder.len(), excess); + + if !remainder.is_empty() { + // Append any excess to the last descriptor. + let last_descriptor = descriptors.last_mut().unwrap(); + last_descriptor.set_size(last_descriptor.size() + remainder.len()); + } + + Ok(Self { + descriptors, + buffer, + }) + } + + /// Consume the buf, returning the descriptors and buffer. + pub fn split(self) -> (&'static mut [DmaDescriptor], &'static mut [u8]) { + (self.descriptors, self.buffer) + } +} + +unsafe impl DmaRxBuffer for DmaRxStreamBuf { + type View = DmaRxStreamBufView; + + fn prepare(&mut self) -> Preparation { + for desc in self.descriptors.iter_mut() { + desc.reset_for_rx(); + } + Preparation { + start: self.descriptors.as_mut_ptr(), + block_size: None, + } + } + + fn into_view(self) -> DmaRxStreamBufView { + DmaRxStreamBufView { + buf: self, + descriptor_idx: 0, + descriptor_offset: 0, + } + } + + fn from_view(view: Self::View) -> Self { + view.buf + } + + fn length(&self) -> usize { + panic!("DmaCircularBuf doesn't have a length") + } +} + +/// A view into a [DmaRxStreamBuf] +pub struct DmaRxStreamBufView { + buf: DmaRxStreamBuf, + descriptor_idx: usize, + descriptor_offset: usize, +} + +impl DmaRxStreamBufView { + /// Returns the number of bytes that are available to read from the buf. + pub fn available_bytes(&self) -> usize { + let (tail, head) = self.buf.descriptors.split_at(self.descriptor_idx); + let mut result = 0; + for desc in head.iter().chain(tail) { + if desc.owner() == Owner::Dma { + break; + } + result += desc.len(); + } + result - self.descriptor_offset + } + + /// Reads as much as possible into the buf from the available data. + pub fn pop(&mut self, buf: &mut [u8]) -> usize { + if buf.is_empty() { + return 0; + } + let total_bytes = buf.len(); + + let mut remaining = buf; + loop { + let available = self.peek(); + if available.len() >= remaining.len() { + remaining.copy_from_slice(&available[0..remaining.len()]); + self.consume(remaining.len()); + let consumed = remaining.len(); + remaining = &mut remaining[consumed..]; + break; + } else { + let to_consume = available.len(); + remaining[0..to_consume].copy_from_slice(available); + self.consume(to_consume); + remaining = &mut remaining[to_consume..]; + } + } + + total_bytes - remaining.len() + } + + /// Returns a slice into the buffer containing available data. + /// This will be the longest possible contiguous slice into the buffer that + /// contains data that is available to read. + /// + /// Note: This function ignores EOFs, see [Self::peek_until_eof] if you need + /// EOF support. + pub fn peek(&self) -> &[u8] { + let (slice, _) = self.peek_internal(false); + slice + } + + /// Same as [Self::peek] but will not skip over any EOFs. + /// + /// It also returns a boolean indicating whether this slice ends with an EOF + /// or not. + pub fn peek_until_eof(&self) -> (&[u8], bool) { + self.peek_internal(true) + } + + /// Consumes the first `n` bytes from the available data, returning any + /// fully consumed descriptors back to the DMA. + /// This is typically called after [Self::peek]/[Self::peek_until_eof]. + /// + /// Returns the number of bytes that were actually consumed. + pub fn consume(&mut self, n: usize) -> usize { + let mut remaining_bytes_to_consume = n; + + loop { + let desc = &mut self.buf.descriptors[self.descriptor_idx]; + + if desc.owner() == Owner::Dma { + // Descriptor is still owned by DMA so it can't be read yet. + // This should only happen when there is no more data available to read. + break; + } + + let remaining_bytes_in_descriptor = desc.len() - self.descriptor_offset; + if remaining_bytes_to_consume < remaining_bytes_in_descriptor { + self.descriptor_offset += remaining_bytes_to_consume; + remaining_bytes_to_consume = 0; + break; + } + + // Reset the descriptor for reuse. + desc.set_owner(Owner::Dma); + desc.set_suc_eof(false); + desc.set_length(0); + + // Before connecting this descriptor to the end of the list, the next descriptor + // must be disconnected from this one to prevent the DMA from + // overtaking. + desc.next = null_mut(); + + let desc_ptr: *mut _ = desc; + + let prev_descriptor_index = self + .descriptor_idx + .checked_sub(1) + .unwrap_or(self.buf.descriptors.len() - 1); + + // Connect this consumed descriptor to the end of the chain. + self.buf.descriptors[prev_descriptor_index].next = desc_ptr; + + self.descriptor_idx += 1; + if self.descriptor_idx >= self.buf.descriptors.len() { + self.descriptor_idx = 0; + } + self.descriptor_offset = 0; + + remaining_bytes_to_consume -= remaining_bytes_in_descriptor; + } + + n - remaining_bytes_to_consume + } + + fn peek_internal(&self, stop_at_eof: bool) -> (&[u8], bool) { + let descriptors = &self.buf.descriptors[self.descriptor_idx..]; + + // There must be at least one descriptor. + debug_assert!(!descriptors.is_empty()); + + if descriptors.len() == 1 { + let last_descriptor = &descriptors[0]; + if last_descriptor.owner() == Owner::Dma { + // No data available. + (&[], false) + } else { + let length = last_descriptor.len() - self.descriptor_offset; + ( + &self.buf.buffer[self.buf.buffer.len() - length..], + last_descriptor.flags.suc_eof(), + ) + } + } else { + let chunk_size = descriptors[0].size(); + let mut found_eof = false; + + let mut number_of_contiguous_bytes = 0; + for desc in descriptors { + if desc.owner() == Owner::Dma { + break; + } + number_of_contiguous_bytes += desc.len(); + + if stop_at_eof && desc.flags.suc_eof() { + found_eof = true; + break; + } + // If the length is smaller than the size, the contiguous-ness ends here. + if desc.len() < desc.size() { + break; + } + } + + ( + &self.buf.buffer[chunk_size * self.descriptor_idx..][..number_of_contiguous_bytes] + [self.descriptor_offset..], + found_eof, + ) + } + } +} diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs index 64e052165..19351d2a3 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -53,13 +53,7 @@ //! //! For convenience you can use the [crate::dma_buffers] macro. -use core::{ - cmp::min, - fmt::Debug, - marker::PhantomData, - ptr::null_mut, - sync::atomic::compiler_fence, -}; +use core::{cmp::min, fmt::Debug, marker::PhantomData, sync::atomic::compiler_fence}; trait Word: crate::private::Sealed {} @@ -356,14 +350,14 @@ impl DmaDescriptor { use enumset::{EnumSet, EnumSetType}; +pub use self::buffers::*; #[cfg(gdma)] pub use self::gdma::*; #[cfg(pdma)] pub use self::pdma::*; -#[cfg(esp32s3)] -use crate::soc::is_slice_in_psram; use crate::{interrupt::InterruptHandler, soc::is_slice_in_dram, Mode}; +mod buffers; #[cfg(gdma)] mod gdma; #[cfg(pdma)] @@ -2133,915 +2127,6 @@ where } } -/// Holds all the information needed to configure a DMA channel for a transfer. -pub struct Preparation { - start: *mut DmaDescriptor, - /// block size for PSRAM transfers (TODO: enable burst mode for non external - /// memory?) - #[cfg_attr(not(esp32s3), allow(dead_code))] - block_size: Option, - // burst_mode, alignment, check_owner, etc. -} - -/// [DmaTxBuffer] is a DMA descriptor + memory combo that can be used for -/// transmitting data from a DMA channel to a peripheral's FIFO. -/// -/// # Safety -/// -/// The implementing type must keep all its descriptors and the buffers they -/// point to valid while the buffer is being transferred. -pub unsafe trait DmaTxBuffer { - /// A type providing operations that are safe to perform on the buffer - /// whilst the DMA is actively using it. - type View; - - /// Prepares the buffer for an imminent transfer and returns - /// information required to use this buffer. - /// - /// Note: This operation is idempotent. - fn prepare(&mut self) -> Preparation; - - /// This is called before the DMA starts using the buffer. - fn into_view(self) -> Self::View; - - /// This is called after the DMA is done using the buffer. - fn from_view(view: Self::View) -> Self; - - /// Returns the maximum number of bytes that would be transmitted by this - /// buffer. - /// - /// This is a convenience hint for SPI. Most peripherals don't care how long - /// the transfer is. - fn length(&self) -> usize; -} - -/// [DmaRxBuffer] is a DMA descriptor + memory combo that can be used for -/// receiving data from a peripheral's FIFO to a DMA channel. -/// -/// Note: Implementations of this trait may only support having a single EOF bit -/// which resides in the last descriptor. There will be a separate trait in -/// future to support multiple EOFs. -/// -/// # Safety -/// -/// The implementing type must keep all its descriptors and the buffers they -/// point to valid while the buffer is being transferred. -pub unsafe trait DmaRxBuffer { - /// A type providing operations that are safe to perform on the buffer - /// whilst the DMA is actively using it. - type View; - - /// Prepares the buffer for an imminent transfer and returns - /// information required to use this buffer. - /// - /// Note: This operation is idempotent. - fn prepare(&mut self) -> Preparation; - - /// This is called before the DMA starts using the buffer. - fn into_view(self) -> Self::View; - - /// This is called after the DMA is done using the buffer. - fn from_view(view: Self::View) -> Self; - - /// Returns the maximum number of bytes that can be received by this buffer. - /// - /// This is a convenience hint for SPI. Most peripherals don't care how long - /// the transfer is. - fn length(&self) -> usize; -} - -/// An in-progress view into [DmaRxBuf]/[DmaTxBuf]. -/// -/// In the future, this could support peeking into state of the -/// descriptors/buffers. -pub struct BufView(T); - -/// Error returned from Dma[Rx|Tx|RxTx]Buf operations. -#[derive(Debug, PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum DmaBufError { - /// More descriptors are needed for the buffer size - InsufficientDescriptors, - /// Descriptors or buffers are not located in a supported memory region - UnsupportedMemoryRegion, - /// Buffer is not aligned to the required size - InvalidAlignment, - /// Invalid chunk size: must be > 0 and <= 4095 - InvalidChunkSize, -} - -/// DMA buffer alignments -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum DmaBufBlkSize { - /// 16 bytes - Size16 = 16, - /// 32 bytes - Size32 = 32, - /// 64 bytes - Size64 = 64, -} - -/// DMA transmit buffer -/// -/// This is a contiguous buffer linked together by DMA descriptors of length -/// 4095 at most. It can only be used for transmitting data to a peripheral's -/// FIFO. See [DmaRxBuf] for receiving data. -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DmaTxBuf { - descriptors: DescriptorSet<'static>, - buffer: &'static mut [u8], - block_size: Option, -} - -impl DmaTxBuf { - /// Creates a new [DmaTxBuf] from some descriptors and a buffer. - /// - /// There must be enough descriptors for the provided buffer. - /// Each descriptor can handle 4092 bytes worth of buffer. - /// - /// Both the descriptors and buffer must be in DMA-capable memory. - /// Only DRAM is supported for descriptors. - pub fn new( - descriptors: &'static mut [DmaDescriptor], - buffer: &'static mut [u8], - ) -> Result { - Self::new_with_block_size(descriptors, buffer, None) - } - - /// Compute max chunk size based on block size - pub const fn compute_chunk_size(block_size: Option) -> usize { - max_chunk_size(block_size) - } - - /// Compute the number of descriptors required for a given block size and - /// buffer size - pub const fn compute_descriptor_count( - buffer_size: usize, - block_size: Option, - ) -> usize { - descriptor_count(buffer_size, Self::compute_chunk_size(block_size), false) - } - - /// Creates a new [DmaTxBuf] from some descriptors and a buffer. - /// - /// There must be enough descriptors for the provided buffer. - /// Each descriptor can handle at most 4095 bytes worth of buffer. - /// Optionally, a block size can be provided for PSRAM & Burst transfers. - /// - /// Both the descriptors and buffer must be in DMA-capable memory. - /// Only DRAM is supported for descriptors. - pub fn new_with_block_size( - descriptors: &'static mut [DmaDescriptor], - buffer: &'static mut [u8], - block_size: Option, - ) -> Result { - cfg_if::cfg_if! { - if #[cfg(esp32s3)] { - // buffer can be either DRAM or PSRAM (if supported) - if !is_slice_in_dram(buffer) && !is_slice_in_psram(buffer) { - return Err(DmaBufError::UnsupportedMemoryRegion); - } - // if its PSRAM, the block_size/alignment must be specified - if is_slice_in_psram(buffer) && block_size.is_none() { - return Err(DmaBufError::InvalidAlignment); - } - } else { - #[cfg(any(esp32,esp32s2))] - if buffer.len() % 4 != 0 && buffer.as_ptr() as usize % 4 != 0 { - // ESP32 requires word alignment for DMA buffers. - // ESP32-S2 technically supports byte-aligned DMA buffers, but the - // transfer ends up writing out of bounds if the buffer's length - // is 2 or 3 (mod 4). - return Err(DmaBufError::InvalidAlignment); - } - // buffer can only be DRAM - if !is_slice_in_dram(buffer) { - return Err(DmaBufError::UnsupportedMemoryRegion); - } - } - } - - let block_size = if is_slice_in_dram(buffer) { - // no need for block size if the buffer is in DRAM - None - } else { - block_size - }; - let mut buf = Self { - descriptors: DescriptorSet::new(descriptors)?, - buffer, - block_size, - }; - - buf.descriptors - .link_with_buffer(buf.buffer, max_chunk_size(block_size))?; - buf.set_length(buf.capacity()); - - Ok(buf) - } - - /// Consume the buf, returning the descriptors and buffer. - pub fn split(self) -> (&'static mut [DmaDescriptor], &'static mut [u8]) { - (self.descriptors.into_inner(), self.buffer) - } - - /// Returns the size of the underlying buffer - pub fn capacity(&self) -> usize { - self.buffer.len() - } - - /// Return the number of bytes that would be transmitted by this buf. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self) -> usize { - self.descriptors - .linked_iter() - .map(|d| d.len()) - .sum::() - } - - /// Reset the descriptors to only transmit `len` amount of bytes from this - /// buf. - /// - /// The number of bytes in data must be less than or equal to the buffer - /// size. - pub fn set_length(&mut self, len: usize) { - assert!(len <= self.buffer.len()); - - unwrap!(self - .descriptors - .set_tx_length(len, max_chunk_size(self.block_size))); - } - - /// Fills the TX buffer with the bytes provided in `data` and reset the - /// descriptors to only cover the filled section. - /// - /// The number of bytes in data must be less than or equal to the buffer - /// size. - pub fn fill(&mut self, data: &[u8]) { - self.set_length(data.len()); - self.as_mut_slice()[..data.len()].copy_from_slice(data); - } - - /// Returns the buf as a mutable slice than can be written. - pub fn as_mut_slice(&mut self) -> &mut [u8] { - self.buffer - } - - /// Returns the buf as a slice than can be read. - pub fn as_slice(&self) -> &[u8] { - self.buffer - } -} - -unsafe impl DmaTxBuffer for DmaTxBuf { - type View = BufView; - - fn prepare(&mut self) -> Preparation { - for desc in self.descriptors.linked_iter_mut() { - // In non-circular mode, we only set `suc_eof` for the last descriptor to signal - // the end of the transfer. - desc.reset_for_tx(desc.next.is_null()); - } - - #[cfg(esp32s3)] - if crate::soc::is_valid_psram_address(self.buffer.as_ptr() as usize) { - unsafe { - crate::soc::cache_writeback_addr( - self.buffer.as_ptr() as u32, - self.buffer.len() as u32, - ) - }; - } - - Preparation { - start: self.descriptors.head(), - block_size: self.block_size, - } - } - - fn into_view(self) -> BufView { - BufView(self) - } - - fn from_view(view: Self::View) -> Self { - view.0 - } - - fn length(&self) -> usize { - self.len() - } -} - -/// DMA receive buffer -/// -/// This is a contiguous buffer linked together by DMA descriptors of length -/// 4092. It can only be used for receiving data from a peripheral's FIFO. -/// See [DmaTxBuf] for transmitting data. -pub struct DmaRxBuf { - descriptors: DescriptorSet<'static>, - buffer: &'static mut [u8], -} - -impl DmaRxBuf { - /// Creates a new [DmaRxBuf] from some descriptors and a buffer. - /// - /// There must be enough descriptors for the provided buffer. - /// Each descriptor can handle 4092 bytes worth of buffer. - /// - /// Both the descriptors and buffer must be in DMA-capable memory. - /// Only DRAM is supported. - pub fn new( - descriptors: &'static mut [DmaDescriptor], - buffer: &'static mut [u8], - ) -> Result { - if !is_slice_in_dram(buffer) { - return Err(DmaBufError::UnsupportedMemoryRegion); - } - - let mut buf = Self { - descriptors: DescriptorSet::new(descriptors)?, - buffer, - }; - - buf.descriptors - .link_with_buffer(buf.buffer, max_chunk_size(None))?; - buf.set_length(buf.capacity()); - - Ok(buf) - } - - /// Consume the buf, returning the descriptors and buffer. - pub fn split(self) -> (&'static mut [DmaDescriptor], &'static mut [u8]) { - (self.descriptors.into_inner(), self.buffer) - } - - /// Returns the size of the underlying buffer - pub fn capacity(&self) -> usize { - self.buffer.len() - } - - /// Returns the maximum number of bytes that this buf has been configured to - /// receive. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self) -> usize { - self.descriptors - .linked_iter() - .map(|d| d.size()) - .sum::() - } - - /// Reset the descriptors to only receive `len` amount of bytes into this - /// buf. - /// - /// The number of bytes in data must be less than or equal to the buffer - /// size. - pub fn set_length(&mut self, len: usize) { - assert!(len <= self.buffer.len()); - - unwrap!(self.descriptors.set_rx_length(len, max_chunk_size(None))); - } - - /// Returns the entire underlying buffer as a slice than can be read. - pub fn as_slice(&self) -> &[u8] { - self.buffer - } - - /// Returns the entire underlying buffer as a slice than can be written. - pub fn as_mut_slice(&mut self) -> &mut [u8] { - self.buffer - } - - /// Return the number of bytes that was received by this buf. - pub fn number_of_received_bytes(&self) -> usize { - self.descriptors - .linked_iter() - .map(|d| d.len()) - .sum::() - } - - /// Reads the received data into the provided `buf`. - /// - /// If `buf.len()` is less than the amount of received data then only the - /// first `buf.len()` bytes of received data is written into `buf`. - /// - /// Returns the number of bytes in written to `buf`. - pub fn read_received_data(&self, mut buf: &mut [u8]) -> usize { - let capacity = buf.len(); - for chunk in self.received_data() { - if buf.is_empty() { - break; - } - let to_fill; - (to_fill, buf) = buf.split_at_mut(chunk.len()); - to_fill.copy_from_slice(chunk); - } - - capacity - buf.len() - } - - /// Returns the received data as an iterator of slices. - pub fn received_data(&self) -> impl Iterator { - self.descriptors.linked_iter().map(|desc| { - // SAFETY: We set up the descriptor to point to a subslice of the buffer, and - // here we are only recreating that slice with a perhaps shorter length. - // We are also not accessing `self.buffer` while this slice is alive, so we - // are not violating any aliasing rules. - unsafe { core::slice::from_raw_parts(desc.buffer.cast_const(), desc.len()) } - }) - } -} - -unsafe impl DmaRxBuffer for DmaRxBuf { - type View = BufView; - - fn prepare(&mut self) -> Preparation { - for desc in self.descriptors.linked_iter_mut() { - desc.reset_for_rx(); - } - - Preparation { - start: self.descriptors.head(), - block_size: None, - } - } - - fn into_view(self) -> BufView { - BufView(self) - } - - fn from_view(view: Self::View) -> Self { - view.0 - } - - fn length(&self) -> usize { - self.len() - } -} - -/// DMA transmit and receive buffer. -/// -/// This is a (single) contiguous buffer linked together by two sets of DMA -/// descriptors of length 4092 each. -/// It can be used for simultaneously transmitting to and receiving from a -/// peripheral's FIFO. These are typically full-duplex transfers. -pub struct DmaRxTxBuf { - rx_descriptors: DescriptorSet<'static>, - tx_descriptors: DescriptorSet<'static>, - buffer: &'static mut [u8], -} - -impl DmaRxTxBuf { - /// Creates a new [DmaRxTxBuf] from some descriptors and a buffer. - /// - /// There must be enough descriptors for the provided buffer. - /// Each descriptor can handle 4092 bytes worth of buffer. - /// - /// Both the descriptors and buffer must be in DMA-capable memory. - /// Only DRAM is supported. - pub fn new( - rx_descriptors: &'static mut [DmaDescriptor], - tx_descriptors: &'static mut [DmaDescriptor], - buffer: &'static mut [u8], - ) -> Result { - if !is_slice_in_dram(buffer) { - return Err(DmaBufError::UnsupportedMemoryRegion); - } - - let mut buf = Self { - rx_descriptors: DescriptorSet::new(rx_descriptors)?, - tx_descriptors: DescriptorSet::new(tx_descriptors)?, - buffer, - }; - buf.rx_descriptors - .link_with_buffer(buf.buffer, max_chunk_size(None))?; - buf.tx_descriptors - .link_with_buffer(buf.buffer, max_chunk_size(None))?; - buf.set_length(buf.capacity()); - - Ok(buf) - } - - /// Consume the buf, returning the rx descriptors, tx descriptors and - /// buffer. - pub fn split( - self, - ) -> ( - &'static mut [DmaDescriptor], - &'static mut [DmaDescriptor], - &'static mut [u8], - ) { - ( - self.rx_descriptors.into_inner(), - self.tx_descriptors.into_inner(), - self.buffer, - ) - } - - /// Return the size of the underlying buffer. - pub fn capacity(&self) -> usize { - self.buffer.len() - } - - /// Return the number of bytes that would be transmitted by this buf. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self) -> usize { - self.tx_descriptors - .linked_iter() - .map(|d| d.len()) - .sum::() - } - - /// Returns the entire buf as a slice than can be read. - pub fn as_slice(&self) -> &[u8] { - self.buffer - } - - /// Returns the entire buf as a slice than can be written. - pub fn as_mut_slice(&mut self) -> &mut [u8] { - self.buffer - } - - /// Reset the descriptors to only transmit/receive `len` amount of bytes - /// with this buf. - /// - /// `len` must be less than or equal to the buffer size. - pub fn set_length(&mut self, len: usize) { - assert!(len <= self.buffer.len()); - - unwrap!(self.rx_descriptors.set_rx_length(len, max_chunk_size(None))); - unwrap!(self.tx_descriptors.set_tx_length(len, max_chunk_size(None))); - } -} - -unsafe impl DmaTxBuffer for DmaRxTxBuf { - type View = BufView; - - fn prepare(&mut self) -> Preparation { - for desc in self.tx_descriptors.linked_iter_mut() { - // In non-circular mode, we only set `suc_eof` for the last descriptor to signal - // the end of the transfer. - desc.reset_for_tx(desc.next.is_null()); - } - - Preparation { - start: self.tx_descriptors.head(), - block_size: None, // TODO: support block size! - } - } - - fn into_view(self) -> BufView { - BufView(self) - } - - fn from_view(view: Self::View) -> Self { - view.0 - } - - fn length(&self) -> usize { - self.len() - } -} - -unsafe impl DmaRxBuffer for DmaRxTxBuf { - type View = BufView; - - fn prepare(&mut self) -> Preparation { - for desc in self.rx_descriptors.linked_iter_mut() { - desc.reset_for_rx(); - } - - Preparation { - start: self.rx_descriptors.head(), - block_size: None, // TODO: support block size! - } - } - - fn into_view(self) -> BufView { - BufView(self) - } - - fn from_view(view: Self::View) -> Self { - view.0 - } - - fn length(&self) -> usize { - self.len() - } -} - -/// DMA Streaming Receive Buffer. -/// -/// This is a contiguous buffer linked together by DMA descriptors, and the -/// buffer is evenly distributed between each descriptor provided. -/// -/// It is used for continuously streaming data from a peripheral's FIFO. -/// -/// It does so by maintaining sliding window of descriptors that progresses when -/// you call [DmaRxStreamBufView::consume]. -/// -/// The list starts out like so `A (empty) -> B (empty) -> C (empty) -> D -/// (empty) -> NULL`. -/// -/// As the DMA writes to the buffers the list progresses like so: -/// - `A (empty) -> B (empty) -> C (empty) -> D (empty) -> NULL` -/// - `A (full) -> B (empty) -> C (empty) -> D (empty) -> NULL` -/// - `A (full) -> B (full) -> C (empty) -> D (empty) -> NULL` -/// - `A (full) -> B (full) -> C (full) -> D (empty) -> NULL` -/// -/// As you call [DmaRxStreamBufView::consume] the list (approximately) -/// progresses like so: -/// - `A (full) -> B (full) -> C (full) -> D (empty) -> NULL` -/// - `B (full) -> C (full) -> D (empty) -> A (empty) -> NULL` -/// - `C (full) -> D (empty) -> A (empty) -> B (empty) -> NULL` -/// - `D (empty) -> A (empty) -> B (empty) -> C (empty) -> NULL` -/// -/// If all the descriptors fill up, the [DmaRxInterrupt::DescriptorEmpty] -/// interrupt will fire and the DMA will stop writing, at which point it is up -/// to you to resume/restart the transfer. -/// -/// Note: This buffer will not tell you when this condition occurs, you should -/// check with the driver to see if the DMA has stopped. -/// -/// When constructing this buffer, it is important to tune the ratio between the -/// chunk size and buffer size appropriately. Smaller chunk sizes means you -/// receive data more frequently but this means the DMA interrupts -/// ([DmaRxInterrupt::Done]) also fire more frequently (if you use them). -/// -/// See [DmaRxStreamBufView] for APIs available whilst a transfer is in -/// progress. -pub struct DmaRxStreamBuf { - descriptors: &'static mut [DmaDescriptor], - buffer: &'static mut [u8], -} - -impl DmaRxStreamBuf { - /// Creates a new [DmaRxStreamBuf] evenly distributing the buffer between - /// the provided descriptors. - pub fn new( - descriptors: &'static mut [DmaDescriptor], - buffer: &'static mut [u8], - ) -> Result { - if !is_slice_in_dram(descriptors) { - return Err(DmaBufError::UnsupportedMemoryRegion); - } - if !is_slice_in_dram(buffer) { - return Err(DmaBufError::UnsupportedMemoryRegion); - } - - if descriptors.is_empty() { - return Err(DmaBufError::InsufficientDescriptors); - } - - // Evenly distribute the buffer between the descriptors. - let chunk_size = buffer.len() / descriptors.len(); - - if chunk_size > 4095 { - return Err(DmaBufError::InsufficientDescriptors); - } - - // Check that the last descriptor can hold the excess - let excess = buffer.len() % descriptors.len(); - if chunk_size + excess > 4095 { - return Err(DmaBufError::InsufficientDescriptors); - } - - // Link up all the descriptors (but not in a circle). - let mut next = null_mut(); - for desc in descriptors.iter_mut().rev() { - desc.next = next; - next = desc; - } - - let mut chunks = buffer.chunks_exact_mut(chunk_size); - for (desc, chunk) in descriptors.iter_mut().zip(chunks.by_ref()) { - desc.buffer = chunk.as_mut_ptr(); - desc.set_size(chunk.len()); - } - - let remainder = chunks.into_remainder(); - debug_assert_eq!(remainder.len(), excess); - - if !remainder.is_empty() { - // Append any excess to the last descriptor. - let last_descriptor = descriptors.last_mut().unwrap(); - last_descriptor.set_size(last_descriptor.size() + remainder.len()); - } - - Ok(Self { - descriptors, - buffer, - }) - } - - /// Consume the buf, returning the descriptors and buffer. - pub fn split(self) -> (&'static mut [DmaDescriptor], &'static mut [u8]) { - (self.descriptors, self.buffer) - } -} - -unsafe impl DmaRxBuffer for DmaRxStreamBuf { - type View = DmaRxStreamBufView; - - fn prepare(&mut self) -> Preparation { - for desc in self.descriptors.iter_mut() { - desc.reset_for_rx(); - } - Preparation { - start: self.descriptors.as_mut_ptr(), - block_size: None, - } - } - - fn into_view(self) -> DmaRxStreamBufView { - DmaRxStreamBufView { - buf: self, - descriptor_idx: 0, - descriptor_offset: 0, - } - } - - fn from_view(view: Self::View) -> Self { - view.buf - } - - fn length(&self) -> usize { - panic!("DmaCircularBuf doesn't have a length") - } -} - -/// A view into a [DmaRxStreamBuf] -pub struct DmaRxStreamBufView { - buf: DmaRxStreamBuf, - descriptor_idx: usize, - descriptor_offset: usize, -} - -impl DmaRxStreamBufView { - /// Returns the number of bytes that are available to read from the buf. - pub fn available_bytes(&self) -> usize { - let (tail, head) = self.buf.descriptors.split_at(self.descriptor_idx); - let mut result = 0; - for desc in head.iter().chain(tail) { - if desc.owner() == Owner::Dma { - break; - } - result += desc.len(); - } - result - self.descriptor_offset - } - - /// Reads as much as possible into the buf from the available data. - pub fn pop(&mut self, buf: &mut [u8]) -> usize { - if buf.is_empty() { - return 0; - } - let total_bytes = buf.len(); - - let mut remaining = buf; - loop { - let available = self.peek(); - if available.len() >= remaining.len() { - remaining.copy_from_slice(&available[0..remaining.len()]); - self.consume(remaining.len()); - let consumed = remaining.len(); - remaining = &mut remaining[consumed..]; - break; - } else { - let to_consume = available.len(); - remaining[0..to_consume].copy_from_slice(available); - self.consume(to_consume); - remaining = &mut remaining[to_consume..]; - } - } - - total_bytes - remaining.len() - } - - /// Returns a slice into the buffer containing available data. - /// This will be the longest possible contiguous slice into the buffer that - /// contains data that is available to read. - /// - /// Note: This function ignores EOFs, see [Self::peek_until_eof] if you need - /// EOF support. - pub fn peek(&self) -> &[u8] { - let (slice, _) = self.peek_internal(false); - slice - } - - /// Same as [Self::peek] but will not skip over any EOFs. - /// - /// It also returns a boolean indicating whether this slice ends with an EOF - /// or not. - pub fn peek_until_eof(&self) -> (&[u8], bool) { - self.peek_internal(true) - } - - /// Consumes the first `n` bytes from the available data, returning any - /// fully consumed descriptors back to the DMA. - /// This is typically called after [Self::peek]/[Self::peek_until_eof]. - /// - /// Returns the number of bytes that were actually consumed. - pub fn consume(&mut self, n: usize) -> usize { - let mut remaining_bytes_to_consume = n; - - loop { - let desc = &mut self.buf.descriptors[self.descriptor_idx]; - - if desc.owner() == Owner::Dma { - // Descriptor is still owned by DMA so it can't be read yet. - // This should only happen when there is no more data available to read. - break; - } - - let remaining_bytes_in_descriptor = desc.len() - self.descriptor_offset; - if remaining_bytes_to_consume < remaining_bytes_in_descriptor { - self.descriptor_offset += remaining_bytes_to_consume; - remaining_bytes_to_consume = 0; - break; - } - - // Reset the descriptor for reuse. - desc.set_owner(Owner::Dma); - desc.set_suc_eof(false); - desc.set_length(0); - - // Before connecting this descriptor to the end of the list, the next descriptor - // must be disconnected from this one to prevent the DMA from - // overtaking. - desc.next = null_mut(); - - let desc_ptr: *mut _ = desc; - - let prev_descriptor_index = self - .descriptor_idx - .checked_sub(1) - .unwrap_or(self.buf.descriptors.len() - 1); - - // Connect this consumed descriptor to the end of the chain. - self.buf.descriptors[prev_descriptor_index].next = desc_ptr; - - self.descriptor_idx += 1; - if self.descriptor_idx >= self.buf.descriptors.len() { - self.descriptor_idx = 0; - } - self.descriptor_offset = 0; - - remaining_bytes_to_consume -= remaining_bytes_in_descriptor; - } - - n - remaining_bytes_to_consume - } - - fn peek_internal(&self, stop_at_eof: bool) -> (&[u8], bool) { - let descriptors = &self.buf.descriptors[self.descriptor_idx..]; - - // There must be at least one descriptor. - debug_assert!(!descriptors.is_empty()); - - if descriptors.len() == 1 { - let last_descriptor = &descriptors[0]; - if last_descriptor.owner() == Owner::Dma { - // No data available. - (&[], false) - } else { - let length = last_descriptor.len() - self.descriptor_offset; - ( - &self.buf.buffer[self.buf.buffer.len() - length..], - last_descriptor.flags.suc_eof(), - ) - } - } else { - let chunk_size = descriptors[0].size(); - let mut found_eof = false; - - let mut number_of_contiguous_bytes = 0; - for desc in descriptors { - if desc.owner() == Owner::Dma { - break; - } - number_of_contiguous_bytes += desc.len(); - - if stop_at_eof && desc.flags.suc_eof() { - found_eof = true; - break; - } - // If the length is smaller than the size, the contiguous-ness ends here. - if desc.len() < desc.size() { - break; - } - } - - ( - &self.buf.buffer[chunk_size * self.descriptor_idx..][..number_of_contiguous_bytes] - [self.descriptor_offset..], - found_eof, - ) - } - } -} - pub(crate) mod dma_private { use super::*;