#[cfg(dma_can_access_psram)]
use core::{mem::MaybeUninit, ops::Range};
use core::{
ops::{Deref, DerefMut},
ptr::{NonNull, null_mut},
};
use super::*;
#[cfg(dma_can_access_psram)]
use crate::soc::{is_slice_in_psram, is_valid_psram_address, is_valid_ram_address};
use crate::{
dma::aligned::{DmaAlignedMut, InternalMemory},
soc::is_slice_in_dram,
};
pub(crate) mod scoped;
pub(crate) use scoped::*;
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DmaBufError {
BufferTooSmall,
InsufficientDescriptors,
UnsupportedMemoryRegion,
InvalidAlignment(DmaAlignmentError),
InvalidChunkSize,
}
impl core::fmt::Display for DmaBufError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
DmaBufError::BufferTooSmall => {
write!(f, "The buffer is smaller than the requested size")
}
DmaBufError::InsufficientDescriptors => {
write!(f, "More descriptors are needed for the buffer size")
}
DmaBufError::UnsupportedMemoryRegion => write!(
f,
"Descriptors or buffers are not located in a supported memory region"
),
DmaBufError::InvalidAlignment(x) => write!(f, "{x}"),
DmaBufError::InvalidChunkSize => {
write!(f, "Invalid chunk size: must be > 0 and <= 4095")
}
}
}
}
impl core::error::Error for DmaBufError {}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DmaAlignmentError {
Address,
Size,
}
impl From<DmaAlignmentError> for DmaBufError {
fn from(err: DmaAlignmentError) -> Self {
DmaBufError::InvalidAlignment(err)
}
}
impl core::fmt::Display for DmaAlignmentError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
DmaAlignmentError::Address => write!(f, "Buffer address is not properly aligned"),
DmaAlignmentError::Size => write!(f, "Buffer size is not properly aligned"),
}
}
}
impl core::error::Error for DmaAlignmentError {}
cfg_select! {
dma_can_access_psram => {
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ExternalBurstConfig {
Size16 = 16,
Size32 = 32,
#[cfg(not(esp32s2))]
Size64 = 64,
}
impl ExternalBurstConfig {
pub const DEFAULT: Self = Self::Size16;
}
impl Default for ExternalBurstConfig {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InternalBurstConfig {
Disabled,
Enabled,
}
impl InternalBurstConfig {
pub const DEFAULT: Self = Self::Disabled;
}
impl Default for InternalBurstConfig {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct BurstConfig {
pub external_memory: ExternalBurstConfig,
pub internal_memory: InternalBurstConfig,
}
impl BurstConfig {
pub const DEFAULT: Self = Self {
external_memory: ExternalBurstConfig::DEFAULT,
internal_memory: InternalBurstConfig::DEFAULT,
};
}
impl Default for BurstConfig {
fn default() -> Self {
Self::DEFAULT
}
}
impl From<InternalBurstConfig> for BurstConfig {
fn from(internal_memory: InternalBurstConfig) -> Self {
Self {
external_memory: ExternalBurstConfig::DEFAULT,
internal_memory,
}
}
}
impl From<ExternalBurstConfig> for BurstConfig {
fn from(external_memory: ExternalBurstConfig) -> Self {
Self {
external_memory,
internal_memory: InternalBurstConfig::DEFAULT,
}
}
}
}
_ => {
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum BurstConfig {
Disabled,
Enabled,
}
impl BurstConfig {
pub const DEFAULT: Self = Self::Disabled;
}
impl Default for BurstConfig {
fn default() -> Self {
Self::DEFAULT
}
}
type InternalBurstConfig = BurstConfig;
}
}
#[cfg(dma_can_access_psram)]
impl ExternalBurstConfig {
const fn min_psram_alignment(self, direction: TransferDirection) -> usize {
if matches!(direction, TransferDirection::In) {
self as usize
} else {
1
}
}
}
impl InternalBurstConfig {
pub(super) const fn is_burst_enabled(self) -> bool {
!matches!(self, Self::Disabled)
}
const fn min_dram_alignment(self, direction: TransferDirection) -> usize {
if matches!(direction, TransferDirection::In) {
if cfg!(esp32) {
4
} else if self.is_burst_enabled() {
4
} else {
1
}
} else {
if cfg!(esp32) {
4
} else {
1
}
}
}
}
const fn max(a: usize, b: usize) -> usize {
if a > b { a } else { b }
}
impl BurstConfig {
delegate::delegate! {
to self.internal_memory {
#[cfg(dma_can_access_psram)]
pub(super) const fn min_dram_alignment(self, direction: TransferDirection) -> usize;
#[cfg(all(dma_can_access_psram, not(esp32s31)))] pub(super) fn is_burst_enabled(self) -> bool;
}
}
pub const fn min_compatible_alignment(self) -> usize {
let in_alignment = self.min_dram_alignment(TransferDirection::In);
let out_alignment = self.min_dram_alignment(TransferDirection::Out);
let alignment = max(in_alignment, out_alignment);
#[cfg(dma_can_access_psram)]
let alignment = max(alignment, self.external_memory as usize);
alignment
}
const fn chunk_size_for_alignment(alignment: usize) -> usize {
4096 - alignment
}
pub const fn max_compatible_chunk_size(self) -> usize {
Self::chunk_size_for_alignment(self.min_compatible_alignment())
}
fn min_alignment(self, _buffer: &[u8], direction: TransferDirection) -> usize {
let alignment = self.min_dram_alignment(direction);
cfg_select! {
dma_can_access_psram => {
let mut alignment = alignment;
if is_valid_psram_address(_buffer.as_ptr() as usize) {
alignment = max(
alignment,
self.external_memory.min_psram_alignment(direction),
);
}
}
_ => {}
}
alignment
}
fn max_chunk_size_for(self, buffer: &[u8], direction: TransferDirection) -> usize {
Self::chunk_size_for_alignment(self.min_alignment(buffer, direction))
}
fn ensure_buffer_aligned(
self,
buffer: &[u8],
direction: TransferDirection,
) -> Result<(), DmaAlignmentError> {
let alignment = self.min_alignment(buffer, direction);
if !(buffer.as_ptr() as usize).is_multiple_of(alignment) {
return Err(DmaAlignmentError::Address);
}
if direction == TransferDirection::In && !buffer.len().is_multiple_of(alignment) {
return Err(DmaAlignmentError::Size);
}
Ok(())
}
fn ensure_buffer_compatible(
self,
buffer: &[u8],
direction: TransferDirection,
) -> Result<(), DmaBufError> {
if buffer.is_empty() {
return Ok(());
}
let is_in_dram = is_slice_in_dram(buffer);
cfg_select! {
dma_can_access_psram => {
let is_in_psram = is_slice_in_psram(buffer);
}
_ => {
let is_in_psram = false;
}
}
if !(is_in_dram || is_in_psram) {
return Err(DmaBufError::UnsupportedMemoryRegion);
}
self.ensure_buffer_aligned(buffer, direction)?;
Ok(())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TransferDirection {
In,
Out,
}
#[derive(PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Preparation {
pub start: *mut DmaDescriptor,
#[cfg(dma_can_access_psram)]
pub accesses_psram: bool,
#[doc = crate::trm_markdown_link!()]
pub burst_transfer: BurstConfig,
pub check_owner: Option<bool>,
pub auto_write_back: bool,
}
pub unsafe trait DmaTxBuffer {
type View;
type Final;
fn prepare(&mut self) -> Preparation;
fn into_view(self) -> Self::View;
fn from_view(view: Self::View) -> Self::Final;
}
pub unsafe trait DmaRxBuffer {
type View;
type Final;
fn prepare(&mut self) -> Preparation;
fn into_view(self) -> Self::View;
fn from_view(view: Self::View) -> Self::Final;
}
pub struct BufView<T>(T);
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DmaTxBuf(ScopedDmaTxBuf<'static>);
impl DmaTxBuf {
pub fn new(
descriptors: DmaAlignedMut<'static, [DmaDescriptor]>,
buffer: DmaAlignedMut<'static, [u8]>,
) -> Result<Self, DmaBufError> {
ScopedDmaTxBuf::new(descriptors, buffer).map(Self)
}
pub fn new_with_config(
descriptors: DmaAlignedMut<'static, [DmaDescriptor]>,
buffer: DmaAlignedMut<'static, [u8]>,
config: impl Into<BurstConfig>,
) -> Result<Self, DmaBufError> {
ScopedDmaTxBuf::new_with_config(descriptors, buffer, config).map(Self)
}
pub fn set_burst_config(&mut self, burst: BurstConfig) -> Result<(), DmaBufError> {
self.0.set_burst_config(burst)
}
pub fn split(
self,
) -> (
DmaAlignedMut<'static, [DmaDescriptor]>,
DmaAlignedMut<'static, [u8]>,
) {
self.0.split()
}
pub fn capacity(&self) -> usize {
self.0.capacity()
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.0.len()
}
pub fn set_length(&mut self, len: usize) {
self.0.set_length(len);
}
pub fn fill(&mut self, data: &[u8]) {
self.0.fill(data);
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
self.0.as_mut_slice()
}
pub fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
pub(crate) fn into_scoped(self) -> ScopedDmaTxBuf<'static> {
self.0
}
}
unsafe impl DmaTxBuffer for DmaTxBuf {
type View = BufView<DmaTxBuf>;
type Final = DmaTxBuf;
fn prepare(&mut self) -> Preparation {
self.0.prepare()
}
fn into_view(self) -> BufView<DmaTxBuf> {
BufView(self)
}
fn from_view(view: Self::View) -> Self {
view.0
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DmaRxBuf(ScopedDmaRxBuf<'static>);
impl DmaRxBuf {
pub fn new(
descriptors: DmaAlignedMut<'static, [DmaDescriptor]>,
buffer: DmaAlignedMut<'static, [u8]>,
) -> Result<Self, DmaBufError> {
ScopedDmaRxBuf::new(descriptors, buffer).map(Self)
}
pub fn new_with_config(
descriptors: DmaAlignedMut<'static, [DmaDescriptor]>,
buffer: DmaAlignedMut<'static, [u8]>,
config: impl Into<BurstConfig>,
) -> Result<Self, DmaBufError> {
ScopedDmaRxBuf::new_with_config(descriptors, buffer, config).map(Self)
}
pub fn set_burst_config(&mut self, burst: BurstConfig) -> Result<(), DmaBufError> {
self.0.set_burst_config(burst)
}
pub fn split(
self,
) -> (
DmaAlignedMut<'static, [DmaDescriptor]>,
DmaAlignedMut<'static, [u8]>,
) {
self.0.split()
}
pub fn capacity(&self) -> usize {
self.0.capacity()
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.0.len()
}
pub fn set_length(&mut self, len: usize) {
self.0.set_length(len)
}
pub fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
self.0.as_mut_slice()
}
pub fn number_of_received_bytes(&self) -> usize {
self.0.number_of_received_bytes()
}
pub fn read_received_data(&self, buf: &mut [u8]) -> usize {
self.0.read_received_data(buf)
}
pub fn received_data(&self) -> impl Iterator<Item = &[u8]> {
self.0.received_data()
}
pub(crate) fn into_scoped(self) -> ScopedDmaRxBuf<'static> {
self.0
}
}
unsafe impl DmaRxBuffer for DmaRxBuf {
type View = BufView<DmaRxBuf>;
type Final = DmaRxBuf;
fn prepare(&mut self) -> Preparation {
self.0.prepare()
}
fn into_view(self) -> BufView<DmaRxBuf> {
BufView(self)
}
fn from_view(view: Self::View) -> Self {
view.0
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DmaRxTxBuf {
rx_descriptors: DescriptorSet<'static>,
tx_descriptors: DescriptorSet<'static>,
buffer: DmaAlignedMut<'static, [u8]>,
burst: BurstConfig,
}
impl DmaRxTxBuf {
pub fn new(
rx_descriptors: DmaAlignedMut<'static, [DmaDescriptor]>,
tx_descriptors: DmaAlignedMut<'static, [DmaDescriptor]>,
buffer: DmaAlignedMut<'static, [u8]>,
) -> Result<Self, DmaBufError> {
let mut buf = Self {
rx_descriptors: DescriptorSet::new(rx_descriptors)?,
tx_descriptors: DescriptorSet::new(tx_descriptors)?,
buffer,
burst: BurstConfig::default(),
};
let capacity = buf.capacity();
buf.configure(buf.burst, capacity)?;
Ok(buf)
}
fn configure(
&mut self,
burst: impl Into<BurstConfig>,
length: usize,
) -> Result<(), DmaBufError> {
let burst = burst.into();
self.set_length_fallible(length, burst)?;
let max_chunk_size_in = burst.max_chunk_size_for(&self.buffer, TransferDirection::In);
let max_chunk_size_out = burst.max_chunk_size_for(&self.buffer, TransferDirection::Out);
self.rx_descriptors
.link_with_buffer(&mut self.buffer, max_chunk_size_in)?;
self.tx_descriptors
.link_with_buffer(&mut self.buffer, max_chunk_size_out)?;
self.burst = burst;
Ok(())
}
pub fn set_burst_config(&mut self, burst: BurstConfig) -> Result<(), DmaBufError> {
let len = self.len();
self.configure(burst, len)
}
#[allow(clippy::type_complexity)]
pub fn split(
self,
) -> (
DmaAlignedMut<'static, [DmaDescriptor]>,
DmaAlignedMut<'static, [DmaDescriptor]>,
DmaAlignedMut<'static, [u8]>,
) {
(
self.rx_descriptors.into_inner(),
self.tx_descriptors.into_inner(),
self.buffer,
)
}
pub fn capacity(&self) -> usize {
self.buffer.len()
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.tx_descriptors
.linked_iter()
.map(|d| d.len())
.sum::<usize>()
}
pub fn as_slice(&self) -> &[u8] {
&self.buffer
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
&mut self.buffer
}
fn set_length_fallible(&mut self, len: usize, burst: BurstConfig) -> Result<(), DmaBufError> {
if len > self.capacity() {
return Err(DmaBufError::BufferTooSmall);
}
burst.ensure_buffer_compatible(&self.buffer[..len], TransferDirection::In)?;
burst.ensure_buffer_compatible(&self.buffer[..len], TransferDirection::Out)?;
let max_chunk_size_in = burst.max_chunk_size_for(&self.buffer, TransferDirection::In);
let max_chunk_size_out = burst.max_chunk_size_for(&self.buffer, TransferDirection::Out);
self.rx_descriptors.set_rx_length(len, max_chunk_size_in)?;
self.tx_descriptors.set_tx_length(len, max_chunk_size_out)?;
Ok(())
}
pub fn set_length(&mut self, len: usize) {
unwrap!(self.set_length_fallible(len, self.burst));
}
}
unsafe impl DmaTxBuffer for DmaRxTxBuf {
type View = BufView<DmaRxTxBuf>;
type Final = DmaRxTxBuf;
fn prepare(&mut self) -> Preparation {
for desc in self.tx_descriptors.linked_iter_mut() {
desc.reset_for_tx(desc.next.is_null());
}
#[cfg(dma_can_access_psram)]
let is_data_in_psram = !is_valid_ram_address(self.buffer.as_ptr() as usize);
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
self.buffer.writeback();
Preparation {
start: self.tx_descriptors.head(),
#[cfg(dma_can_access_psram)]
accesses_psram: is_data_in_psram,
burst_transfer: self.burst,
check_owner: None,
auto_write_back: false,
}
}
fn into_view(self) -> BufView<DmaRxTxBuf> {
BufView(self)
}
fn from_view(view: Self::View) -> Self {
view.0
}
}
unsafe impl DmaRxBuffer for DmaRxTxBuf {
type View = BufView<DmaRxTxBuf>;
type Final = DmaRxTxBuf;
fn prepare(&mut self) -> Preparation {
for desc in self.rx_descriptors.linked_iter_mut() {
desc.reset_for_rx();
}
cfg_select! {
dma_can_access_psram => {
let is_data_in_psram = !is_valid_ram_address(self.buffer.as_ptr() as usize);
if is_data_in_psram || cfg!(soc_internal_memory_cached) {
unsafe {
crate::soc::cache_invalidate_addr(
self.buffer.as_ptr() as u32,
self.buffer.len() as u32,
)
};
}
}
_ => {}
}
Preparation {
start: self.rx_descriptors.head(),
#[cfg(dma_can_access_psram)]
accesses_psram: is_data_in_psram,
burst_transfer: self.burst,
check_owner: None,
auto_write_back: true,
}
}
fn into_view(self) -> BufView<DmaRxTxBuf> {
BufView(self)
}
fn from_view(view: Self::View) -> Self {
view.0
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DmaRxStreamBuf {
descriptors: DmaAlignedMut<'static, [DmaDescriptor]>,
buffer: DmaAlignedMut<'static, [u8]>,
burst: BurstConfig,
}
impl DmaRxStreamBuf {
pub fn new(
mut descriptors: DmaAlignedMut<'static, [DmaDescriptor]>,
mut buffer: DmaAlignedMut<'static, [u8]>,
) -> Result<Self, DmaBufError> {
if descriptors.len() < 4 {
return Err(DmaBufError::InsufficientDescriptors);
}
let chunk_size = Some(buffer.len() / descriptors.len())
.filter(|x| *x <= 4095)
.ok_or(DmaBufError::InsufficientDescriptors)?;
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();
if !remainder.is_empty() {
let last_descriptor = descriptors.last_mut().unwrap();
let size = last_descriptor.size() + remainder.len();
if size > 4095 {
return Err(DmaBufError::InsufficientDescriptors);
}
last_descriptor.set_size(size);
}
Ok(Self {
descriptors,
buffer,
burst: BurstConfig::default(),
})
}
pub fn split(
self,
) -> (
DmaAlignedMut<'static, [DmaDescriptor]>,
DmaAlignedMut<'static, [u8]>,
) {
(self.descriptors, self.buffer)
}
}
unsafe impl DmaRxBuffer for DmaRxStreamBuf {
type View = DmaRxStreamBufView;
type Final = DmaRxStreamBuf;
fn prepare(&mut self) -> Preparation {
let mut next = null_mut();
for desc in self.descriptors.iter_mut().rev() {
desc.next = next;
next = desc;
desc.reset_for_rx();
}
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
self.descriptors.writeback();
Preparation {
start: self.descriptors.as_mut_ptr(),
#[cfg(dma_can_access_psram)]
accesses_psram: false,
burst_transfer: self.burst,
check_owner: None,
auto_write_back: true,
}
}
fn into_view(self) -> DmaRxStreamBufView {
DmaRxStreamBufView {
buf: self,
descriptor_idx: 0,
descriptor_offset: 0,
}
}
fn from_view(view: Self::View) -> Self {
view.buf
}
}
pub struct DmaRxStreamBufView {
buf: DmaRxStreamBuf,
descriptor_idx: usize,
descriptor_offset: usize,
}
impl DmaRxStreamBufView {
pub fn available_bytes(&mut self) -> usize {
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
self.buf.descriptors.invalidate();
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
}
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.is_empty() {
break;
}
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()
}
pub fn peek(&mut self) -> &[u8] {
let (slice, _) = self.peek_internal(false);
slice
}
pub fn peek_until_eof(&mut self) -> (&[u8], bool) {
self.peek_internal(true)
}
pub fn consume(&mut self, n: usize) -> usize {
let mut remaining_bytes_to_consume = n;
let mut descriptors_modified = false;
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
self.buf.descriptors.invalidate();
loop {
let desc = &mut self.buf.descriptors[self.descriptor_idx];
if desc.owner() == Owner::Dma {
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;
}
desc.set_owner(Owner::Dma);
desc.set_suc_eof(false);
desc.set_length(0);
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);
self.buf.descriptors[prev_descriptor_index].next = desc_ptr;
descriptors_modified = true;
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;
}
if descriptors_modified {
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
self.buf.descriptors.writeback();
}
n - remaining_bytes_to_consume
}
fn peek_internal(&mut self, stop_at_eof: bool) -> (&[u8], bool) {
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
self.buf.descriptors.invalidate();
let descriptors = &self.buf.descriptors[self.descriptor_idx..];
debug_assert!(!descriptors.is_empty());
if descriptors.len() == 1 {
let last_descriptor = &descriptors[0];
if last_descriptor.owner() == Owner::Dma {
(&[], false)
} else {
let length = last_descriptor.len() - self.descriptor_offset;
let chunk_size = last_descriptor.size();
let buffer_start = self.buf.buffer.len() - chunk_size;
#[cfg(soc_internal_memory_cached)]
if length != 0 {
unsafe {
crate::soc::cache_invalidate_addr(
self.buf.buffer.as_ptr().add(buffer_start) as u32,
length as u32,
);
}
}
(
&self.buf.buffer[buffer_start..][..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 desc.len() < desc.size() {
break;
}
}
#[cfg(soc_internal_memory_cached)]
{
let buffer_start = chunk_size * self.descriptor_idx + self.descriptor_offset;
let buffer_len = number_of_contiguous_bytes - self.descriptor_offset;
if buffer_len != 0 {
unsafe {
crate::soc::cache_invalidate_addr(
self.buf.buffer.as_ptr().add(buffer_start) as u32,
buffer_len as u32,
);
}
}
}
(
&self.buf.buffer[chunk_size * self.descriptor_idx..][..number_of_contiguous_bytes]
[self.descriptor_offset..],
found_eof,
)
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DmaTxStreamBuf {
descriptors: DmaAlignedMut<'static, [DmaDescriptor]>,
buffer: DmaAlignedMut<'static, [u8]>,
burst: BurstConfig,
pre_filled: Option<usize>,
view_descriptor_idx: usize,
view_descriptor_offset: usize,
}
impl DmaTxStreamBuf {
pub fn new(
mut descriptors: DmaAlignedMut<'static, [DmaDescriptor]>,
mut buffer: DmaAlignedMut<'static, [u8]>,
) -> Result<Self, DmaBufError> {
if descriptors.len() < 4 {
return Err(DmaBufError::InsufficientDescriptors);
}
let chunk_size = Some(buffer.len() / descriptors.len())
.filter(|x| *x <= 4095)
.ok_or(DmaBufError::InsufficientDescriptors)?;
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());
desc.set_length(chunk.len());
}
let remainder = chunks.into_remainder();
if !remainder.is_empty() {
let last_descriptor = descriptors.last_mut().unwrap();
let size = last_descriptor.size() + remainder.len();
if size > 4095 {
Err(DmaBufError::InsufficientDescriptors)?;
}
last_descriptor.set_size(size);
}
Ok(Self {
descriptors,
buffer,
burst: Default::default(),
pre_filled: None,
view_descriptor_idx: 0,
view_descriptor_offset: 0,
})
}
pub fn split(
self,
) -> (
DmaAlignedMut<'static, [DmaDescriptor]>,
DmaAlignedMut<'static, [u8]>,
) {
(self.descriptors, self.buffer)
}
pub fn push(&mut self, data: &[u8]) -> usize {
self.push_with(|buf| {
let len = buf.len().min(data.len());
buf[..len].copy_from_slice(&data[..len]);
len
})
}
pub fn push_with(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> usize {
let start = self.pre_filled.unwrap_or(0);
let bytes_pushed = f(&mut self.buffer[start..]);
self.pre_filled = Some(start + bytes_pushed);
bytes_pushed
}
fn setup_view_state(&mut self) {
let pre_filled = self.pre_filled.unwrap_or(self.buffer.len());
let (idx, offset) = mark_tx_stream_descriptors_ready(&mut self.descriptors, pre_filled);
self.view_descriptor_idx = idx;
self.view_descriptor_offset = offset;
#[cfg(soc_internal_memory_cached)]
if pre_filled != 0 {
unsafe {
crate::soc::cache_writeback_addr(self.buffer.as_ptr() as u32, pre_filled as u32);
}
}
}
}
fn mark_tx_stream_descriptors_ready(
descriptors: &mut DmaAlignedMut<'_, [DmaDescriptor]>,
bytes_pushed: usize,
) -> (usize, usize) {
if bytes_pushed == 0 {
return (0, 0);
}
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
descriptors.invalidate();
let num = descriptors.len();
let mut bytes_filled = 0;
let mut cursor = (0, 0);
for d in 0..num {
let remaining = bytes_pushed - bytes_filled;
let size = descriptors[d].size();
if remaining == 0 {
terminate_tx_stream_at(descriptors, d);
cursor = (d, 0);
break;
}
if remaining < size {
if d == 0 {
descriptors[d].set_owner(Owner::Dma);
descriptors[d].set_length(remaining);
descriptors[d].set_suc_eof(true);
if num > 1 {
terminate_tx_stream_at(descriptors, 1);
cursor = (1, 0);
} else {
descriptors[d].next = null_mut();
}
} else {
terminate_tx_stream_at(descriptors, d);
cursor = (d, remaining);
}
break;
}
bytes_filled += size;
descriptors[d].set_owner(Owner::Dma);
descriptors[d].set_length(size);
descriptors[d].set_suc_eof(true);
}
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
descriptors.writeback();
cursor
}
fn terminate_tx_stream_at(descriptors: &mut DmaAlignedMut<'_, [DmaDescriptor]>, start: usize) {
if start > 0 {
descriptors[start - 1].next = null_mut();
}
for desc in descriptors.iter_mut().skip(start) {
desc.set_owner(Owner::Cpu);
}
}
fn advance_tx_stream_descriptors(
descriptors: &mut DmaAlignedMut<'_, [DmaDescriptor]>,
descriptor_idx: &mut usize,
descriptor_offset: &mut usize,
bytes_pushed: usize,
) {
if bytes_pushed == 0 {
return;
}
let mut bytes_filled = 0;
let num_descriptors = descriptors.len();
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
descriptors.invalidate();
for i in 0..num_descriptors {
let d = (*descriptor_idx + i) % num_descriptors;
let desc = &mut descriptors[d];
let bytes_in_d = desc.size() - *descriptor_offset;
if bytes_in_d + bytes_filled > bytes_pushed {
*descriptor_idx = d;
*descriptor_offset = *descriptor_offset + bytes_pushed - bytes_filled;
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
descriptors.writeback();
return;
}
bytes_filled += bytes_in_d;
*descriptor_offset = 0;
desc.set_owner(Owner::Dma);
desc.set_length(desc.size());
desc.set_suc_eof(true);
let p = d.checked_sub(1).unwrap_or(num_descriptors - 1);
if p != d {
let [prev, desc] = descriptors.get_disjoint_mut([p, d]).unwrap();
desc.next = null_mut();
prev.next = desc;
}
}
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
descriptors.writeback();
}
unsafe impl DmaTxBuffer for DmaTxStreamBuf {
type View = DmaTxStreamBufView;
type Final = Self;
fn prepare(&mut self) -> Preparation {
let mut next = null_mut();
for desc in self.descriptors.iter_mut().rev() {
desc.next = next;
desc.set_owner(Owner::Dma);
next = desc;
}
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
self.descriptors.writeback();
self.setup_view_state();
Preparation {
start: self.descriptors.as_mut_ptr(),
#[cfg(dma_can_access_psram)]
accesses_psram: false,
burst_transfer: self.burst,
check_owner: None,
auto_write_back: true,
}
}
fn into_view(self) -> Self::View {
DmaTxStreamBufView {
descriptor_idx: self.view_descriptor_idx,
descriptor_offset: self.view_descriptor_offset,
buf: self,
}
}
fn from_view(view: Self::View) -> Self {
let DmaTxStreamBufView {
mut buf,
descriptor_idx,
descriptor_offset,
} = view;
buf.view_descriptor_idx = descriptor_idx;
buf.view_descriptor_offset = descriptor_offset;
buf
}
}
pub struct DmaTxStreamBufView {
buf: DmaTxStreamBuf,
descriptor_idx: usize,
descriptor_offset: usize,
}
impl DmaTxStreamBufView {
pub fn available_bytes(&mut self) -> usize {
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
self.buf.descriptors.invalidate();
let (tail, head) = self.buf.descriptors.split_at(self.descriptor_idx);
head.iter()
.chain(tail)
.take_while(|d| d.owner() == Owner::Cpu)
.map(|d| d.size())
.sum::<usize>()
.saturating_sub(self.descriptor_offset)
}
fn write_position(&self) -> usize {
let desc = &self.buf.descriptors[self.descriptor_idx];
desc.buffer
.addr()
.wrapping_sub(self.buf.buffer.as_ptr().addr())
+ self.descriptor_offset
}
pub fn push_with(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> usize {
let dma_start = self.write_position();
let dma_end = dma_start
.saturating_add(self.available_bytes())
.min(self.buf.buffer.len())
.max(dma_start);
let bytes_pushed = f(&mut self.buf.buffer[dma_start..dma_end]).min(dma_end - dma_start);
#[cfg(soc_internal_memory_cached)]
if bytes_pushed != 0 {
unsafe {
crate::soc::cache_writeback_addr(
self.buf.buffer.as_ptr().add(dma_start) as u32,
bytes_pushed as u32,
);
}
}
self.advance(bytes_pushed);
bytes_pushed
}
pub fn advance(&mut self, bytes_pushed: usize) {
advance_tx_stream_descriptors(
&mut self.buf.descriptors,
&mut self.descriptor_idx,
&mut self.descriptor_offset,
bytes_pushed,
);
}
pub fn push(&mut self, data: &[u8]) -> usize {
let total_len = data.len();
let mut remaining = data;
while !remaining.is_empty() && self.available_bytes() > 0 {
let written = self.push_with(|buffer| {
let len = usize::min(buffer.len(), remaining.len());
buffer[..len].copy_from_slice(&remaining[..len]);
len
});
if written == 0 {
break;
}
remaining = &remaining[written..];
}
total_len - remaining.len()
}
}
static mut EMPTY: InternalMemory<[DmaDescriptor; 1]> = InternalMemory::new([DmaDescriptor::EMPTY]);
pub struct EmptyBuf;
unsafe impl DmaTxBuffer for EmptyBuf {
type View = EmptyBuf;
type Final = EmptyBuf;
fn prepare(&mut self) -> Preparation {
#[cfg(soc_internal_memory_cached)]
#[allow(static_mut_refs)]
unsafe {
EMPTY.get_mut().writeback();
}
Preparation {
start: (&raw mut EMPTY).cast(),
#[cfg(dma_can_access_psram)]
accesses_psram: false,
burst_transfer: BurstConfig::default(),
check_owner: Some(false),
auto_write_back: false,
}
}
fn into_view(self) -> EmptyBuf {
self
}
fn from_view(view: Self::View) -> Self {
view
}
}
unsafe impl DmaRxBuffer for EmptyBuf {
type View = EmptyBuf;
type Final = EmptyBuf;
fn prepare(&mut self) -> Preparation {
#[cfg(soc_internal_memory_cached)]
#[allow(static_mut_refs)]
unsafe {
EMPTY.get_mut().writeback();
}
Preparation {
start: (&raw mut EMPTY).cast(),
#[cfg(dma_can_access_psram)]
accesses_psram: false,
burst_transfer: BurstConfig::default(),
check_owner: Some(false),
auto_write_back: true,
}
}
fn into_view(self) -> EmptyBuf {
self
}
fn from_view(view: Self::View) -> Self {
view
}
}
pub struct DmaLoopBuf {
descriptor: DmaAlignedMut<'static, [DmaDescriptor]>,
buffer: DmaAlignedMut<'static, [u8]>,
}
impl DmaLoopBuf {
pub fn new(
mut descriptors: DmaAlignedMut<'static, [DmaDescriptor]>,
mut buffer: DmaAlignedMut<'static, [u8]>,
) -> Result<DmaLoopBuf, DmaBufError> {
if buffer.len() > BurstConfig::default().max_chunk_size_for(&buffer, TransferDirection::Out)
{
return Err(DmaBufError::InsufficientDescriptors);
}
descriptors[0].set_owner(Owner::Dma); descriptors[0].set_suc_eof(false);
descriptors[0].set_length(buffer.len());
descriptors[0].set_size(buffer.len());
descriptors[0].buffer = buffer.as_mut_ptr();
descriptors[0].next = descriptors.as_mut_ptr();
Ok(Self {
descriptor: descriptors,
buffer,
})
}
pub fn split(
self,
) -> (
DmaAlignedMut<'static, [DmaDescriptor]>,
DmaAlignedMut<'static, [u8]>,
) {
(self.descriptor, self.buffer)
}
}
unsafe impl DmaTxBuffer for DmaLoopBuf {
type View = DmaLoopBuf;
type Final = DmaLoopBuf;
fn prepare(&mut self) -> Preparation {
Preparation {
start: self.descriptor.as_mut_ptr(),
#[cfg(dma_can_access_psram)]
accesses_psram: false,
burst_transfer: BurstConfig::default(),
check_owner: Some(false),
auto_write_back: false,
}
}
fn into_view(self) -> Self::View {
self
}
fn from_view(view: Self::View) -> Self {
view
}
}
impl Deref for DmaLoopBuf {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.buffer
}
}
impl DerefMut for DmaLoopBuf {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer
}
}
pub(crate) struct NoBuffer(pub(crate) Preparation);
impl NoBuffer {
fn prep(&self) -> Preparation {
Preparation {
start: self.0.start,
#[cfg(dma_can_access_psram)]
accesses_psram: self.0.accesses_psram,
burst_transfer: self.0.burst_transfer,
check_owner: self.0.check_owner,
auto_write_back: self.0.auto_write_back,
}
}
}
unsafe impl DmaTxBuffer for NoBuffer {
type View = ();
type Final = ();
fn prepare(&mut self) -> Preparation {
self.prep()
}
fn into_view(self) -> Self::View {}
fn from_view(_view: Self::View) {}
}
unsafe impl DmaRxBuffer for NoBuffer {
type View = ();
type Final = ();
fn prepare(&mut self) -> Preparation {
self.prep()
}
fn into_view(self) -> Self::View {}
fn from_view(_view: Self::View) {}
}
#[cfg_attr(not(any(aes_supports_dma, spi_master_supports_dma)), expect(unused))]
pub(crate) unsafe fn prepare_for_tx(
descriptors: &mut [DmaDescriptor],
mut data: NonNull<[u8]>,
block_size: usize,
) -> Result<(NoBuffer, usize), DmaError> {
let alignment =
BurstConfig::DEFAULT.min_alignment(unsafe { data.as_ref() }, TransferDirection::Out);
if !data.addr().get().is_multiple_of(alignment) {
return Err(DmaError::InvalidAlignment(DmaAlignmentError::Address));
}
let alignment = alignment.max(block_size);
let chunk_size = 4096 - alignment;
let data_len = data.len().min(chunk_size * descriptors.len());
cfg_select! {
dma_can_access_psram => {
let data_addr = data.addr().get();
let data_in_psram = crate::psram::psram_range().contains(&data_addr);
if data_in_psram || cfg!(soc_internal_memory_cached) {
unsafe { crate::soc::cache_writeback_addr(data_addr as u32, data_len as u32) };
}
}
soc_internal_memory_cached => {
unsafe { crate::soc::cache_writeback_addr(data.addr().get() as u32, data_len as u32) };
}
_ => {}
}
let descriptors = unsafe { DmaAlignedMut::new_unchecked(descriptors) };
let mut descriptors = unwrap!(DescriptorSet::new(descriptors));
unwrap!(descriptors.link_with_buffer(unsafe { data.as_mut() }, chunk_size));
unwrap!(descriptors.set_tx_length(data_len, chunk_size));
for desc in descriptors.linked_iter_mut() {
desc.reset_for_tx(desc.next.is_null());
}
#[cfg(soc_internal_memory_cached)]
descriptors.descriptors.writeback();
Ok((
NoBuffer(Preparation {
start: descriptors.head(),
burst_transfer: BurstConfig::DEFAULT,
check_owner: None,
auto_write_back: false,
#[cfg(dma_can_access_psram)]
accesses_psram: data_in_psram,
}),
data_len,
))
}
#[cfg_attr(not(any(aes_supports_dma, spi_master_supports_dma)), expect(unused))]
pub(crate) unsafe fn prepare_for_rx(
descriptors: &mut [DmaDescriptor],
#[cfg(dma_can_access_psram)] align_buffers: &mut [Option<ManualWritebackBuffer>; 2],
mut data: NonNull<[u8]>,
) -> (NoBuffer, usize) {
let chunk_size =
BurstConfig::DEFAULT.max_chunk_size_for(unsafe { data.as_ref() }, TransferDirection::In);
cfg_select! {
dma_can_access_psram => {
let data_addr = data.addr().get();
let data_in_psram = crate::psram::psram_range().contains(&data_addr);
}
_ => {
let data_in_psram = false;
}
}
let descriptors = unsafe { DmaAlignedMut::new_unchecked(descriptors) };
let mut descriptors = unwrap!(DescriptorSet::new(descriptors));
let data_len = if data_in_psram {
cfg_select! {
dma_can_access_psram => {
let consumed_bytes =
build_descriptor_list_for_psram(&mut descriptors, align_buffers, data);
unsafe {
crate::soc::cache_writeback_addr(data_addr as u32, consumed_bytes as u32);
crate::soc::cache_invalidate_addr(data_addr as u32, consumed_bytes as u32);
}
consumed_bytes
}
_ => {
unreachable!()
}
}
} else {
let data_len = data.len();
unwrap!(descriptors.link_with_buffer(unsafe { data.as_mut() }, chunk_size));
unwrap!(descriptors.set_tx_length(data_len, chunk_size));
#[cfg(soc_internal_memory_cached)]
unsafe {
crate::soc::cache_writeback_addr(data.addr().get() as u32, data_len as u32);
crate::soc::cache_invalidate_addr(data.addr().get() as u32, data_len as u32);
}
data_len
};
for desc in descriptors.linked_iter_mut() {
desc.reset_for_rx();
}
#[cfg(soc_internal_memory_cached)]
descriptors.descriptors.writeback();
(
NoBuffer(Preparation {
start: descriptors.head(),
burst_transfer: BurstConfig::DEFAULT,
check_owner: None,
auto_write_back: true,
#[cfg(dma_can_access_psram)]
accesses_psram: data_in_psram,
}),
data_len,
)
}
#[cfg(dma_can_access_psram)]
fn build_descriptor_list_for_psram(
descriptors: &mut DescriptorSet<'_>,
copy_buffers: &mut [Option<ManualWritebackBuffer>; 2],
data: NonNull<[u8]>,
) -> usize {
let data_len = data.len();
let data_addr = data.addr().get();
let min_alignment = ExternalBurstConfig::DEFAULT.min_psram_alignment(TransferDirection::In);
let chunk_size = 4096 - min_alignment;
let mut desciptor_iter = DescriptorChainingIter::new(&mut descriptors.descriptors);
let mut copy_buffer_iter = copy_buffers.iter_mut();
let has_aligned_data = data_len > BUF_LEN;
let offset = data_addr % min_alignment;
let head_to_copy = min_alignment - offset;
let head_to_copy = if !has_aligned_data {
BUF_LEN
} else if head_to_copy > 0 && head_to_copy < MIN_LAST_DMA_LEN {
head_to_copy + min_alignment
} else {
head_to_copy
};
let head_to_copy = head_to_copy.min(data_len);
let tail_to_copy = (data_len - head_to_copy) % min_alignment;
let tail_to_copy = if tail_to_copy > 0 && tail_to_copy < MIN_LAST_DMA_LEN {
tail_to_copy + min_alignment
} else {
tail_to_copy
};
let mut consumed = 0;
if head_to_copy > 0 {
let copy_buffer = unwrap!(copy_buffer_iter.next());
let buffer =
copy_buffer.insert(ManualWritebackBuffer::new(get_range(data, 0..head_to_copy)));
buffer.prepare_for_dma();
let Some(descriptor) = desciptor_iter.next() else {
return consumed;
};
descriptor.set_size(head_to_copy);
descriptor.buffer = buffer.mut_buffer_ptr();
consumed += head_to_copy;
};
let mut aligned_data = get_range(data, head_to_copy..data.len() - tail_to_copy);
while !aligned_data.is_empty() {
let Some(descriptor) = desciptor_iter.next() else {
return consumed;
};
let chunk = aligned_data.len().min(chunk_size);
descriptor.set_size(chunk);
descriptor.buffer = aligned_data.cast::<u8>().as_ptr();
consumed += chunk;
aligned_data = get_range(aligned_data, chunk..aligned_data.len());
}
if tail_to_copy > 0 {
let copy_buffer = unwrap!(copy_buffer_iter.next());
let buffer = copy_buffer.insert(ManualWritebackBuffer::new(get_range(
data,
data.len() - tail_to_copy..data.len(),
)));
buffer.prepare_for_dma();
let Some(descriptor) = desciptor_iter.next() else {
return consumed;
};
descriptor.set_size(tail_to_copy);
descriptor.buffer = buffer.mut_buffer_ptr();
consumed += tail_to_copy;
}
consumed
}
#[cfg(dma_can_access_psram)]
fn get_range(ptr: NonNull<[u8]>, range: Range<usize>) -> NonNull<[u8]> {
let len = range.end - range.start;
NonNull::slice_from_raw_parts(unsafe { ptr.cast().byte_add(range.start) }, len)
}
#[cfg(dma_can_access_psram)]
struct DescriptorChainingIter<'a> {
index: usize,
descriptors: &'a mut [DmaDescriptor],
}
#[cfg(dma_can_access_psram)]
impl<'a> DescriptorChainingIter<'a> {
fn new(descriptors: &'a mut [DmaDescriptor]) -> Self {
Self {
descriptors,
index: 0,
}
}
fn next(&mut self) -> Option<&'_ mut DmaDescriptor> {
if self.index == 0 {
self.index += 1;
self.descriptors.get_mut(0)
} else if self.index < self.descriptors.len() {
let index = self.index;
self.index += 1;
let ptr = &raw mut self.descriptors[index];
self.descriptors[index - 1].next = ptr;
Some(unsafe { &mut *ptr })
} else {
None
}
}
}
#[cfg(dma_can_access_psram)]
const MIN_LAST_DMA_LEN: usize = if cfg!(esp32s2) { 5 } else { 1 };
#[cfg(dma_can_access_psram)]
const BUF_LEN: usize = 16 + 2 * (MIN_LAST_DMA_LEN - 1);
#[cfg(dma_can_access_psram)]
pub(crate) struct ManualWritebackBuffer {
buffer: InternalMemory<MaybeUninit<[u8; BUF_LEN]>>,
dst_address: NonNull<u8>,
n_bytes: u8,
}
#[cfg(dma_can_access_psram)]
impl ManualWritebackBuffer {
pub fn new(ptr: NonNull<[u8]>) -> Self {
assert!(ptr.len() <= BUF_LEN);
Self {
buffer: InternalMemory::new(MaybeUninit::uninit()),
dst_address: ptr.cast(),
n_bytes: ptr.len() as u8,
}
}
pub fn prepare_for_dma(&mut self) {
#[cfg(soc_internal_memory_cached)]
self.buffer.get_mut().invalidate();
}
pub fn write_back(&mut self) {
#[cfg(soc_internal_memory_cached)]
self.buffer.get_mut().invalidate();
let src = self.mut_buffer_ptr().cast_const();
unsafe {
self.dst_address
.as_ptr()
.copy_from(src, self.n_bytes as usize);
}
}
pub fn mut_buffer_ptr(&mut self) -> *mut u8 {
self.buffer.get_mut().as_mut_ptr().cast::<u8>()
}
}