[DMA 8/8] Burst configuration (#2543)

* Implement burst configuration

* Reject transfers if the DMA in incapable of accessing external memory

* Fix psram

* Rename, documentation

* Address a few review comments

* Check buffer length, too

* Return error instead of panic

* Allow users to only specify the relevant burst config

* Add missing conversion

* Add missing docs

* Fix IN alignment requirements

* Fix test

* Deduplicate chunk size literal
This commit is contained in:
Dániel Buga 2024-12-06 09:30:58 +01:00 committed by GitHub
parent 2d4ccd735f
commit 9458fd3ed4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 835 additions and 361 deletions

View File

@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `OutputOpenDrain::unlisten` (#2625)
- Added `{Input, Flex}::wait_for` (#2625)
- Peripheral singletons now implement `Debug`, `PartialEq`, `defmt::Format` and `Eq` (except AnyPeripherals) (#2682)
- `BurstConfig`, a device-specific configuration for configuring DMA transfers in burst mode (#2543)
- `{DmaRxBuf, DmaTxBuf, DmaRxTxBuf}::set_burst_config` (#2543)
### Changed
@ -76,6 +78,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The `Dma` structure has been removed. (#2545)
- Removed `embedded-hal 0.2.x` impls and deps from `esp-hal` (#2593)
- Removed `Camera::set_` functions (#2610)
- `DmaTxBuf::{compute_chunk_size, compute_descriptor_count, new_with_block_size}` (#2543)
## [0.22.0] - 2024-11-20

View File

@ -22,10 +22,10 @@ by `esp_hal::init()`. The channels themselves have been renamed to match other p
+let channel = peripherals.DMA_CH2;
```
### Configuration changes
### Channel configuration changes
- `configure_for_async` and `configure` have been removed
- PDMA devices (ESP32, ESP32-S2) provide no configurability
- PDMA devices (ESP32, ESP32-S2) provide no channel configurability
- GDMA devices provide `set_priority` to change DMA in/out channel priority
```diff
@ -49,6 +49,12 @@ by `esp_hal::init()`. The channels themselves have been renamed to match other p
+.with_dma(dma_channel);
```
### Burst mode configuration
Burst mode is now a property of buffers, instead of DMA channels. Configuration can be done by
calling `set_burst_config` on buffers that support it. The configuration options and the
corresponding `BurstConfig` type are device specfic.
### Usability changes affecting applications
Individual channels are no longer wrapped in `Channel`, but they implement the `DmaChannel` trait.

View File

@ -4,24 +4,340 @@ use core::{
};
use super::*;
use crate::soc::is_slice_in_dram;
#[cfg(esp32s3)]
use crate::soc::is_slice_in_psram;
use crate::soc::{is_slice_in_dram, is_slice_in_psram};
#[cfg(psram_dma)]
use crate::soc::{is_valid_psram_address, is_valid_ram_address};
/// Burst transfer configuration.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
/// Error returned from Dma[Rx|Tx|RxTx]Buf operations.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum BurstConfig {
/// Burst mode is disabled.
Disabled,
pub enum DmaBufError {
/// The buffer is smaller than the requested size.
BufferTooSmall,
/// Burst mode is enabled.
Enabled,
/// More descriptors are needed for the buffer size.
InsufficientDescriptors,
/// Descriptors or buffers are not located in a supported memory region.
UnsupportedMemoryRegion,
/// Buffer address or size is not properly aligned.
InvalidAlignment(DmaAlignmentError),
/// Invalid chunk size: must be > 0 and <= 4095.
InvalidChunkSize,
}
/// DMA buffer alignment errors.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DmaAlignmentError {
/// Buffer address is not properly aligned.
Address,
/// Buffer size is not properly aligned.
Size,
}
impl From<DmaAlignmentError> for DmaBufError {
fn from(err: DmaAlignmentError) -> Self {
DmaBufError::InvalidAlignment(err)
}
}
cfg_if::cfg_if! {
if #[cfg(psram_dma)] {
/// Burst size used when transferring to and from external memory.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ExternalBurstConfig {
/// 16 bytes
Size16 = 16,
/// 32 bytes
Size32 = 32,
/// 64 bytes
Size64 = 64,
}
impl ExternalBurstConfig {
/// The default external memory burst length.
pub const DEFAULT: Self = Self::Size16;
}
impl Default for ExternalBurstConfig {
fn default() -> Self {
Self::DEFAULT
}
}
/// Internal memory access burst mode.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InternalBurstConfig {
/// Burst mode is disabled.
Disabled,
/// Burst mode is enabled.
Enabled,
}
impl InternalBurstConfig {
/// The default internal burst mode configuration.
pub const DEFAULT: Self = Self::Disabled;
}
impl Default for InternalBurstConfig {
fn default() -> Self {
Self::DEFAULT
}
}
/// Burst transfer configuration.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct BurstConfig {
/// Configures the burst size for PSRAM transfers.
///
/// Burst mode is always enabled for PSRAM transfers.
pub external_memory: ExternalBurstConfig,
/// Enables or disables the burst mode for internal memory transfers.
///
/// The burst size is not configurable.
pub internal_memory: InternalBurstConfig,
}
impl BurstConfig {
/// The default burst mode configuration.
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,
}
}
}
} else {
/// Burst transfer configuration.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum BurstConfig {
/// Burst mode is disabled.
Disabled,
/// Burst mode is enabled.
Enabled,
}
impl BurstConfig {
/// The default burst mode configuration.
pub const DEFAULT: Self = Self::Disabled;
}
impl Default for BurstConfig {
fn default() -> Self {
Self::DEFAULT
}
}
type InternalBurstConfig = BurstConfig;
}
}
#[cfg(psram_dma)]
impl ExternalBurstConfig {
const fn min_psram_alignment(self, direction: TransferDirection) -> usize {
// S2 TRM: Specifically, size and buffer address pointer in receive descriptors
// should be 16-byte, 32-byte or 64-byte aligned. For data frame whose
// length is not a multiple of 16 bytes, 32 bytes, or 64 bytes, EDMA adds
// padding bytes to the end.
// S3 TRM: Size and Address for IN transfers must be block aligned. For receive
// descriptors, if the data length received are not aligned with block size,
// GDMA will pad the data received with 0 until they are aligned to
// initiate burst transfer. You can read the length field in receive descriptors
// to obtain the length of valid data received
if matches!(direction, TransferDirection::In) {
self as usize
} else {
// S2 TRM: Size, length and buffer address pointer in transmit descriptors are
// not necessarily aligned with block size.
// S3 TRM: Size, length, and buffer address pointer in transmit descriptors do
// not need to be aligned.
1
}
}
}
impl InternalBurstConfig {
pub(super) const fn is_burst_enabled(self) -> bool {
!matches!(self, Self::Disabled)
}
// Size and address alignment as those come in pairs on current hardware.
const fn min_dram_alignment(self, direction: TransferDirection) -> usize {
if matches!(direction, TransferDirection::In) {
// NOTE(danielb): commenting this check is incorrect as per TRM, but works.
// we'll need to restore this once peripherals can read a
// different amount of data than what is configured in the
// buffer.
// if cfg!(esp32) {
// // NOTE: The size must be word-aligned.
// // NOTE: The buffer address must be word-aligned
// 4
// }
if self.is_burst_enabled() {
// As described in "Accessing Internal Memory" paragraphs in the various TRMs.
4
} else {
1
}
} else {
// OUT transfers have no alignment requirements, except for ESP32, which is
// described below.
if cfg!(esp32) {
// SPI DMA: Burst transmission is supported. The data size for
// a single transfer must be four bytes aligned.
// I2S DMA: Burst transfer is supported. However, unlike the
// SPI DMA channels, the data size for a single transfer is
// one word, or four bytes.
4
} else {
1
}
}
}
}
const fn max(a: usize, b: usize) -> usize {
if a > b {
a
} else {
b
}
}
impl BurstConfig {
pub(super) fn is_burst_enabled(self) -> bool {
!matches!(self, Self::Disabled)
delegate::delegate! {
#[cfg(psram_dma)]
to self.internal_memory {
pub(super) const fn min_dram_alignment(self, direction: TransferDirection) -> usize;
pub(super) fn is_burst_enabled(self) -> bool;
}
}
/// Calculates an alignment that is compatible with the current burst
/// configuration.
///
/// This is an over-estimation so that Descriptors can be safely used with
/// any DMA channel in any direction.
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(psram_dma)]
let alignment = max(alignment, self.external_memory as usize);
alignment
}
const fn chunk_size_for_alignment(alignment: usize) -> usize {
// DMA descriptors have a 12-bit field for the size/length of the buffer they
// point at. As there is no such thing as 0-byte alignment, this means the
// maximum size is 4095 bytes.
4096 - alignment
}
/// Calculates a chunk size that is compatible with the current burst
/// configuration's alignment requirements.
///
/// This is an over-estimation so that Descriptors can be safely used with
/// any DMA channel in any direction.
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_if::cfg_if! {
if #[cfg(psram_dma)] {
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
}
// Note: this function ignores address alignment as we assume the buffers are
// aligned.
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 % alignment != 0 {
return Err(DmaAlignmentError::Address);
}
// NB: the TRMs suggest that buffer length don't need to be aligned, but
// for IN transfers, we configure the DMA descriptors' size field, which needs
// to be aligned.
if direction == TransferDirection::In && buffer.len() % alignment != 0 {
return Err(DmaAlignmentError::Size);
}
Ok(())
}
fn ensure_buffer_compatible(
self,
buffer: &[u8],
direction: TransferDirection,
) -> Result<(), DmaBufError> {
// buffer can be either DRAM or PSRAM (if supported)
let is_in_dram = is_slice_in_dram(buffer);
let is_in_psram = cfg!(psram_dma) && is_slice_in_psram(buffer);
if !(is_in_dram || is_in_psram) {
return Err(DmaBufError::UnsupportedMemoryRegion);
}
self.ensure_buffer_aligned(buffer, direction)?;
Ok(())
}
}
@ -36,6 +352,8 @@ pub enum TransferDirection {
}
/// Holds all the information needed to configure a DMA channel for a transfer.
#[derive(PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Preparation {
/// The descriptor the DMA will start from.
pub start: *mut DmaDescriptor,
@ -43,29 +361,18 @@ pub struct Preparation {
/// The direction of the DMA transfer.
pub direction: TransferDirection,
/// Block size for PSRAM transfers.
///
/// If the buffer is in PSRAM, the implementation must ensure the following:
///
/// - The implementation of the buffer must provide a non-`None` block size.
/// - For [`TransferDirection::In`] transfers, the implementation of the
/// buffer must invalidate the cache that contains the buffer before the
/// DMA starts.
/// - For [`TransferDirection::Out`] transfers, the implementation of the
/// buffer must write back the cache that contains the buffer before the
/// DMA starts.
#[cfg(esp32s3)]
pub external_memory_block_size: Option<DmaBufBlkSize>,
/// Must be `true` if any of the DMA descriptors contain data in PSRAM.
#[cfg(psram_dma)]
pub accesses_psram: bool,
/// Configures the DMA to transfer data in bursts.
///
/// The implementation of the buffer must ensure that burst mode is only
/// enabled when alignment requirements are met.
/// The implementation of the buffer must ensure that buffer size
/// and alignment in each descriptor is compatible with the burst
/// transfer configuration.
///
/// There are no additional alignment requirements for
/// [`TransferDirection::Out`] burst transfers, but
/// [`TransferDirection::In`] transfers require all descriptors to have
/// buffer pointers and sizes that are a multiple of 4 (word aligned).
/// For details on alignment requirements, refer to your chip's
#[doc = crate::trm_markdown_link!()]
pub burst_transfer: BurstConfig,
/// Configures the "check owner" feature of the DMA channel.
@ -157,32 +464,6 @@ pub unsafe trait DmaRxBuffer {
/// descriptors/buffers.
pub struct BufView<T>(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
@ -193,14 +474,15 @@ pub enum DmaBufBlkSize {
pub struct DmaTxBuf {
descriptors: DescriptorSet<'static>,
buffer: &'static mut [u8],
block_size: Option<DmaBufBlkSize>,
burst: BurstConfig,
}
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.
/// Depending on alignment requirements, each descriptor can handle at most
/// 4095 bytes worth of buffer.
///
/// Both the descriptors and buffer must be in DMA-capable memory.
/// Only DRAM is supported for descriptors.
@ -208,81 +490,57 @@ impl DmaTxBuf {
descriptors: &'static mut [DmaDescriptor],
buffer: &'static mut [u8],
) -> Result<Self, DmaBufError> {
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<DmaBufBlkSize>) -> 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<DmaBufBlkSize>,
) -> usize {
descriptor_count(buffer_size, Self::compute_chunk_size(block_size), false)
Self::new_with_config(descriptors, buffer, BurstConfig::default())
}
/// 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.
/// Depending on alignment requirements, each descriptor can handle at most
/// 4095 bytes worth of buffer.
///
/// Both the descriptors and buffer must be in DMA-capable memory.
/// Only DRAM is supported for descriptors.
pub fn new_with_block_size(
pub fn new_with_config(
descriptors: &'static mut [DmaDescriptor],
buffer: &'static mut [u8],
block_size: Option<DmaBufBlkSize>,
config: impl Into<BurstConfig>,
) -> Result<Self, DmaBufError> {
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,
burst: BurstConfig::default(),
};
buf.descriptors
.link_with_buffer(buf.buffer, max_chunk_size(block_size))?;
buf.set_length(buf.capacity());
let capacity = buf.capacity();
buf.configure(config, 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)?;
self.descriptors.link_with_buffer(
self.buffer,
burst.max_chunk_size_for(self.buffer, TransferDirection::Out),
)?;
self.burst = burst;
Ok(())
}
/// Configures the DMA to use burst transfers to access this buffer.
pub fn set_burst_config(&mut self, burst: BurstConfig) -> Result<(), DmaBufError> {
let len = self.len();
self.configure(burst, len)
}
/// Consume the buf, returning the descriptors and buffer.
pub fn split(self) -> (&'static mut [DmaDescriptor], &'static mut [u8]) {
(self.descriptors.into_inner(), self.buffer)
@ -302,17 +560,25 @@ impl DmaTxBuf {
.sum::<usize>()
}
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::Out)?;
self.descriptors.set_tx_length(
len,
burst.max_chunk_size_for(self.buffer, TransferDirection::Out),
)
}
/// 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)));
unwrap!(self.set_length_fallible(len, self.burst))
}
/// Fills the TX buffer with the bytes provided in `data` and reset the
@ -346,23 +612,26 @@ unsafe impl DmaTxBuffer for DmaTxBuf {
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,
)
};
cfg_if::cfg_if! {
if #[cfg(psram_dma)] {
let is_data_in_psram = !is_valid_ram_address(self.buffer.as_ptr() as usize);
if is_data_in_psram {
unsafe {
crate::soc::cache_writeback_addr(
self.buffer.as_ptr() as u32,
self.buffer.len() as u32,
)
};
}
}
}
Preparation {
start: self.descriptors.head(),
direction: TransferDirection::Out,
#[cfg(esp32s3)]
external_memory_block_size: self.block_size,
// TODO: support burst transfers.
burst_transfer: BurstConfig::Disabled,
#[cfg(psram_dma)]
accesses_psram: is_data_in_psram,
burst_transfer: self.burst,
check_owner: None,
}
}
@ -384,6 +653,7 @@ unsafe impl DmaTxBuffer for DmaTxBuf {
pub struct DmaRxBuf {
descriptors: DescriptorSet<'static>,
buffer: &'static mut [u8],
burst: BurstConfig,
}
impl DmaRxBuf {
@ -398,22 +668,40 @@ impl DmaRxBuf {
descriptors: &'static mut [DmaDescriptor],
buffer: &'static mut [u8],
) -> Result<Self, DmaBufError> {
if !is_slice_in_dram(buffer) {
return Err(DmaBufError::UnsupportedMemoryRegion);
}
let mut buf = Self {
descriptors: DescriptorSet::new(descriptors)?,
buffer,
burst: BurstConfig::default(),
};
buf.descriptors
.link_with_buffer(buf.buffer, max_chunk_size(None))?;
buf.set_length(buf.capacity());
buf.configure(buf.burst, buf.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)?;
self.descriptors.link_with_buffer(
self.buffer,
burst.max_chunk_size_for(self.buffer, TransferDirection::In),
)?;
self.burst = burst;
Ok(())
}
/// Configures the DMA to use burst transfers to access this buffer.
pub fn set_burst_config(&mut self, burst: BurstConfig) -> Result<(), DmaBufError> {
let len = self.len();
self.configure(burst, len)
}
/// Consume the buf, returning the descriptors and buffer.
pub fn split(self) -> (&'static mut [DmaDescriptor], &'static mut [u8]) {
(self.descriptors.into_inner(), self.buffer)
@ -434,15 +722,25 @@ impl DmaRxBuf {
.sum::<usize>()
}
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)?;
self.descriptors.set_rx_length(
len,
burst.max_chunk_size_for(&self.buffer[..len], TransferDirection::In),
)
}
/// 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)));
unwrap!(self.set_length_fallible(len, self.burst));
}
/// Returns the entire underlying buffer as a slice than can be read.
@ -503,18 +801,27 @@ unsafe impl DmaRxBuffer for DmaRxBuf {
desc.reset_for_rx();
}
cfg_if::cfg_if! {
if #[cfg(psram_dma)] {
// Optimization: avoid locking for PSRAM range.
let is_data_in_psram = !is_valid_ram_address(self.buffer.as_ptr() as usize);
if is_data_in_psram {
unsafe {
crate::soc::cache_invalidate_addr(
self.buffer.as_ptr() as u32,
self.buffer.len() as u32,
)
};
}
}
}
Preparation {
start: self.descriptors.head(),
direction: TransferDirection::In,
// TODO: support external memory access.
#[cfg(esp32s3)]
external_memory_block_size: None,
// TODO: DmaRxBuf doesn't currently enforce the alignment requirements required for
// bursting. In the future, it could either enforce the alignment or
// calculate if the alignment requirements happen to be met.
burst_transfer: BurstConfig::Disabled,
#[cfg(psram_dma)]
accesses_psram: is_data_in_psram,
burst_transfer: self.burst,
check_owner: None,
}
}
@ -538,6 +845,7 @@ pub struct DmaRxTxBuf {
rx_descriptors: DescriptorSet<'static>,
tx_descriptors: DescriptorSet<'static>,
buffer: &'static mut [u8],
burst: BurstConfig,
}
impl DmaRxTxBuf {
@ -553,24 +861,47 @@ impl DmaRxTxBuf {
tx_descriptors: &'static mut [DmaDescriptor],
buffer: &'static mut [u8],
) -> Result<Self, DmaBufError> {
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,
burst: BurstConfig::default(),
};
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());
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)?;
self.rx_descriptors.link_with_buffer(
self.buffer,
burst.max_chunk_size_for(self.buffer, TransferDirection::In),
)?;
self.tx_descriptors.link_with_buffer(
self.buffer,
burst.max_chunk_size_for(self.buffer, TransferDirection::Out),
)?;
self.burst = burst;
Ok(())
}
/// Configures the DMA to use burst transfers to access this buffer.
pub fn set_burst_config(&mut self, burst: BurstConfig) -> Result<(), DmaBufError> {
let len = self.len();
self.configure(burst, len)
}
/// Consume the buf, returning the rx descriptors, tx descriptors and
/// buffer.
pub fn split(
@ -611,15 +942,31 @@ impl DmaRxTxBuf {
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)?;
self.rx_descriptors.set_rx_length(
len,
burst.max_chunk_size_for(self.buffer, TransferDirection::In),
)?;
self.tx_descriptors.set_tx_length(
len,
burst.max_chunk_size_for(self.buffer, TransferDirection::Out),
)?;
Ok(())
}
/// 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)));
unwrap!(self.set_length_fallible(len, self.burst));
}
}
@ -633,16 +980,27 @@ unsafe impl DmaTxBuffer for DmaRxTxBuf {
desc.reset_for_tx(desc.next.is_null());
}
cfg_if::cfg_if! {
if #[cfg(psram_dma)] {
// Optimization: avoid locking for PSRAM range.
let is_data_in_psram = !is_valid_ram_address(self.buffer.as_ptr() as usize);
if is_data_in_psram {
unsafe {
crate::soc::cache_writeback_addr(
self.buffer.as_ptr() as u32,
self.buffer.len() as u32,
)
};
}
}
}
Preparation {
start: self.tx_descriptors.head(),
direction: TransferDirection::Out,
// TODO: support external memory access.
#[cfg(esp32s3)]
external_memory_block_size: None,
// TODO: This is TX, the DMA channel is free to do a burst transfer.
burst_transfer: BurstConfig::Disabled,
#[cfg(psram_dma)]
accesses_psram: is_data_in_psram,
burst_transfer: self.burst,
check_owner: None,
}
}
@ -664,17 +1022,27 @@ unsafe impl DmaRxBuffer for DmaRxTxBuf {
desc.reset_for_rx();
}
cfg_if::cfg_if! {
if #[cfg(psram_dma)] {
// Optimization: avoid locking for PSRAM range.
let is_data_in_psram = !is_valid_ram_address(self.buffer.as_ptr() as usize);
if is_data_in_psram {
unsafe {
crate::soc::cache_invalidate_addr(
self.buffer.as_ptr() as u32,
self.buffer.len() as u32,
)
};
}
}
}
Preparation {
start: self.rx_descriptors.head(),
direction: TransferDirection::In,
// TODO: support external memory access.
#[cfg(esp32s3)]
external_memory_block_size: None,
// TODO: DmaRxTxBuf doesn't currently enforce the alignment requirements required for
// bursting.
burst_transfer: BurstConfig::Disabled,
#[cfg(psram_dma)]
accesses_psram: is_data_in_psram,
burst_transfer: self.burst,
check_owner: None,
}
}
@ -731,6 +1099,7 @@ unsafe impl DmaRxBuffer for DmaRxTxBuf {
pub struct DmaRxStreamBuf {
descriptors: &'static mut [DmaDescriptor],
buffer: &'static mut [u8],
burst: BurstConfig,
}
impl DmaRxStreamBuf {
@ -789,6 +1158,7 @@ impl DmaRxStreamBuf {
Ok(Self {
descriptors,
buffer,
burst: BurstConfig::default(),
})
}
@ -808,14 +1178,9 @@ unsafe impl DmaRxBuffer for DmaRxStreamBuf {
Preparation {
start: self.descriptors.as_mut_ptr(),
direction: TransferDirection::In,
// TODO: support external memory access.
#[cfg(esp32s3)]
external_memory_block_size: None,
// TODO: DmaRxStreamBuf doesn't currently enforce the alignment requirements required
// for bursting.
burst_transfer: BurstConfig::Disabled,
#[cfg(psram_dma)]
accesses_psram: false,
burst_transfer: self.burst,
// Whilst we give ownership of the descriptors the DMA, the correctness of this buffer
// implementation doesn't rely on the DMA checking for descriptor ownership.
@ -1022,9 +1387,9 @@ unsafe impl DmaTxBuffer for EmptyBuf {
Preparation {
start: unsafe { core::ptr::addr_of_mut!(EMPTY).cast() },
direction: TransferDirection::Out,
#[cfg(esp32s3)]
external_memory_block_size: None,
burst_transfer: BurstConfig::Disabled,
#[cfg(psram_dma)]
accesses_psram: false,
burst_transfer: BurstConfig::default(),
// As we don't give ownership of the descriptor to the DMA, it's important that the DMA
// channel does *NOT* check for ownership, otherwise the channel will return an error.
@ -1049,9 +1414,9 @@ unsafe impl DmaRxBuffer for EmptyBuf {
Preparation {
start: unsafe { core::ptr::addr_of_mut!(EMPTY).cast() },
direction: TransferDirection::In,
#[cfg(esp32s3)]
external_memory_block_size: None,
burst_transfer: BurstConfig::Disabled,
#[cfg(psram_dma)]
accesses_psram: false,
burst_transfer: BurstConfig::default(),
// As we don't give ownership of the descriptor to the DMA, it's important that the DMA
// channel does *NOT* check for ownership, otherwise the channel will return an error.
@ -1096,7 +1461,8 @@ impl DmaLoopBuf {
return Err(DmaBufError::UnsupportedMemoryRegion);
}
if buffer.len() > max_chunk_size(None) {
if buffer.len() > BurstConfig::default().max_chunk_size_for(buffer, TransferDirection::Out)
{
return Err(DmaBufError::InsufficientDescriptors);
}
@ -1122,11 +1488,10 @@ unsafe impl DmaTxBuffer for DmaLoopBuf {
fn prepare(&mut self) -> Preparation {
Preparation {
start: self.descriptor,
#[cfg(psram_dma)]
accesses_psram: false,
direction: TransferDirection::Out,
// TODO: support external memory access.
#[cfg(esp32s3)]
external_memory_block_size: None,
burst_transfer: BurstConfig::Disabled,
burst_transfer: BurstConfig::default(),
// The DMA must not check the owner bit, as it is never set.
check_owner: Some(false),
}

View File

@ -194,6 +194,11 @@ impl RegisterAccess for AnyGdmaTxChannel {
.out_conf1()
.modify(|_, w| unsafe { w.out_ext_mem_bk_size().bits(size as u8) });
}
#[cfg(psram_dma)]
fn can_access_psram(&self) -> bool {
true
}
}
impl TxRegisterAccess for AnyGdmaTxChannel {
@ -431,6 +436,11 @@ impl RegisterAccess for AnyGdmaRxChannel {
.in_conf1()
.modify(|_, w| unsafe { w.in_ext_mem_bk_size().bits(size as u8) });
}
#[cfg(psram_dma)]
fn can_access_psram(&self) -> bool {
true
}
}
impl RxRegisterAccess for AnyGdmaRxChannel {

View File

@ -1,5 +1,3 @@
#[cfg(esp32s3)]
use crate::dma::DmaExtMemBKSize;
use crate::{
dma::{
dma_private::{DmaSupport, DmaSupportRx},
@ -152,21 +150,6 @@ where
.prepare_transfer_without_start(self.peripheral, &self.rx_chain)?;
self.channel.rx.set_mem2mem_mode(true);
}
#[cfg(esp32s3)]
{
let align = match unsafe { crate::soc::cache_get_dcache_line_size() } {
16 => DmaExtMemBKSize::Size16,
32 => DmaExtMemBKSize::Size32,
64 => DmaExtMemBKSize::Size64,
_ => panic!("unsupported cache line size"),
};
if crate::soc::is_valid_psram_address(tx_ptr as usize) {
self.channel.tx.set_ext_mem_block_size(align);
}
if crate::soc::is_valid_psram_address(rx_ptr as usize) {
self.channel.rx.set_ext_mem_block_size(align);
}
}
self.channel.tx.start_transfer()?;
self.channel.rx.start_transfer()?;
Ok(DmaTransferRx::new(self))

View File

@ -64,7 +64,7 @@ use crate::{
interrupt::InterruptHandler,
peripheral::{Peripheral, PeripheralRef},
peripherals::Interrupt,
soc::is_slice_in_dram,
soc::{is_slice_in_dram, is_valid_memory_address, is_valid_ram_address},
system,
Async,
Blocking,
@ -675,6 +675,14 @@ macro_rules! dma_buffers_impl {
)
}
}};
($size:expr, is_circular = $circular:tt) => {
$crate::dma_buffers_impl!(
$size,
$crate::dma::BurstConfig::DEFAULT.max_compatible_chunk_size(),
is_circular = $circular
);
};
}
#[doc(hidden)]
@ -722,7 +730,6 @@ macro_rules! dma_descriptor_count {
/// ```rust,no_run
#[doc = crate::before_snippet!()]
/// use esp_hal::dma_tx_buffer;
/// use esp_hal::dma::DmaBufBlkSize;
///
/// let tx_buf = dma_tx_buffer!(32000);
/// # }
@ -730,11 +737,7 @@ macro_rules! dma_descriptor_count {
#[macro_export]
macro_rules! dma_tx_buffer {
($tx_size:expr) => {{
let (tx_buffer, tx_descriptors) = $crate::dma_buffers_impl!(
$tx_size,
$crate::dma::DmaTxBuf::compute_chunk_size(None),
is_circular = false
);
let (tx_buffer, tx_descriptors) = $crate::dma_buffers_impl!($tx_size, is_circular = false);
$crate::dma::DmaTxBuf::new(tx_descriptors, tx_buffer)
}};
@ -794,11 +797,11 @@ macro_rules! dma_loop_buffer {
}
/// DMA Errors
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DmaError {
/// The alignment of data is invalid
InvalidAlignment,
InvalidAlignment(DmaAlignmentError),
/// More descriptors are needed for the buffer size
OutOfDescriptors,
/// DescriptorError the DMA rejected the descriptor configuration. This
@ -824,8 +827,9 @@ impl From<DmaBufError> for DmaError {
match error {
DmaBufError::InsufficientDescriptors => DmaError::OutOfDescriptors,
DmaBufError::UnsupportedMemoryRegion => DmaError::UnsupportedMemoryRegion,
DmaBufError::InvalidAlignment => DmaError::InvalidAlignment,
DmaBufError::InvalidAlignment(err) => DmaError::InvalidAlignment(err),
DmaBufError::InvalidChunkSize => DmaError::InvalidChunkSize,
DmaBufError::BufferTooSmall => DmaError::BufferTooSmall,
}
}
}
@ -1016,10 +1020,10 @@ impl DescriptorChain {
len: usize,
prepare_descriptor: impl Fn(&mut DmaDescriptor, usize),
) -> Result<(), DmaError> {
if !crate::soc::is_valid_ram_address(self.first() as usize)
|| !crate::soc::is_valid_ram_address(self.last() as usize)
|| !crate::soc::is_valid_memory_address(data as usize)
|| !crate::soc::is_valid_memory_address(unsafe { data.add(len) } as usize)
if !is_valid_ram_address(self.first() as usize)
|| !is_valid_ram_address(self.last() as usize)
|| !is_valid_memory_address(data as usize)
|| !is_valid_memory_address(unsafe { data.add(len) } as usize)
{
return Err(DmaError::UnsupportedMemoryRegion);
}
@ -1066,17 +1070,6 @@ pub const fn descriptor_count(buffer_size: usize, chunk_size: usize, is_circular
buffer_size.div_ceil(chunk_size)
}
/// Compute max chunk size based on block size.
const fn max_chunk_size(block_size: Option<DmaBufBlkSize>) -> usize {
match block_size {
Some(size) => 4096 - size as usize,
#[cfg(esp32)]
None => 4092, // esp32 requires 4 byte alignment
#[cfg(not(esp32))]
None => 4095,
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
struct DescriptorSet<'a> {
@ -1272,6 +1265,7 @@ impl<'a> DescriptorSet<'a> {
}
/// Block size for transfers to/from PSRAM
#[cfg(psram_dma)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum DmaExtMemBKSize {
/// External memory block size of 16 bytes.
@ -1282,12 +1276,13 @@ pub enum DmaExtMemBKSize {
Size64 = 2,
}
impl From<DmaBufBlkSize> for DmaExtMemBKSize {
fn from(size: DmaBufBlkSize) -> Self {
#[cfg(psram_dma)]
impl From<ExternalBurstConfig> for DmaExtMemBKSize {
fn from(size: ExternalBurstConfig) -> Self {
match size {
DmaBufBlkSize::Size16 => DmaExtMemBKSize::Size16,
DmaBufBlkSize::Size32 => DmaExtMemBKSize::Size32,
DmaBufBlkSize::Size64 => DmaExtMemBKSize::Size64,
ExternalBurstConfig::Size16 => DmaExtMemBKSize::Size16,
ExternalBurstConfig::Size32 => DmaExtMemBKSize::Size32,
ExternalBurstConfig::Size64 => DmaExtMemBKSize::Size64,
}
}
}
@ -1758,9 +1753,6 @@ pub trait Rx: crate::private::Sealed {
fn stop_transfer(&mut self);
#[cfg(esp32s3)]
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize);
#[cfg(gdma)]
fn set_mem2mem_mode(&mut self, value: bool);
@ -1916,6 +1908,17 @@ where
) -> Result<(), DmaError> {
debug_assert_eq!(preparation.direction, TransferDirection::In);
debug!("Preparing RX transfer {:?}", preparation);
trace!("First descriptor {:?}", unsafe { &*preparation.start });
#[cfg(psram_dma)]
if preparation.accesses_psram && !self.rx_impl.can_access_psram() {
return Err(DmaError::UnsupportedMemoryRegion);
}
#[cfg(psram_dma)]
self.rx_impl
.set_ext_mem_block_size(preparation.burst_transfer.external_memory.into());
self.rx_impl.set_burst_mode(preparation.burst_transfer);
self.rx_impl.set_descr_burst_mode(true);
self.rx_impl.set_check_owner(preparation.check_owner);
@ -1950,36 +1953,43 @@ where
peri: DmaPeripheral,
chain: &DescriptorChain,
) -> Result<(), DmaError> {
// For ESP32-S3 we check each descriptor buffer that points to PSRAM for
// We check each descriptor buffer that points to PSRAM for
// alignment and invalidate the cache for that buffer.
// NOTE: for RX the `buffer` and `size` need to be aligned but the `len` does
// not. TRM section 3.4.9
// Note that DmaBuffer implementations are required to do this for us.
#[cfg(esp32s3)]
for des in chain.descriptors.iter() {
// we are forcing the DMA alignment to the cache line size
// required when we are using dcache
let alignment = crate::soc::cache_get_dcache_line_size() as usize;
if crate::soc::is_valid_psram_address(des.buffer as usize) {
// both the size and address of the buffer must be aligned
if des.buffer as usize % alignment != 0 && des.size() % alignment != 0 {
return Err(DmaError::InvalidAlignment);
cfg_if::cfg_if! {
if #[cfg(psram_dma)] {
let mut uses_psram = false;
let psram_range = crate::soc::psram_range();
for des in chain.descriptors.iter() {
// we are forcing the DMA alignment to the cache line size
// required when we are using dcache
let alignment = crate::soc::cache_get_dcache_line_size() as usize;
if crate::soc::addr_in_range(des.buffer as usize, psram_range.clone()) {
uses_psram = true;
// both the size and address of the buffer must be aligned
if des.buffer as usize % alignment != 0 {
return Err(DmaError::InvalidAlignment(DmaAlignmentError::Address));
}
if des.size() % alignment != 0 {
return Err(DmaError::InvalidAlignment(DmaAlignmentError::Size));
}
crate::soc::cache_invalidate_addr(des.buffer as u32, des.size() as u32);
}
}
crate::soc::cache_invalidate_addr(des.buffer as u32, des.size() as u32);
}
}
self.do_prepare(
Preparation {
start: chain.first().cast_mut(),
#[cfg(esp32s3)]
external_memory_block_size: None,
direction: TransferDirection::In,
burst_transfer: BurstConfig::Disabled,
check_owner: Some(false),
},
peri,
)
let preparation = Preparation {
start: chain.first().cast_mut(),
direction: TransferDirection::In,
#[cfg(psram_dma)]
accesses_psram: uses_psram,
burst_transfer: BurstConfig::default(),
check_owner: Some(false),
};
self.do_prepare(preparation, peri)
}
unsafe fn prepare_transfer<BUF: DmaRxBuffer>(
@ -2009,11 +2019,6 @@ where
self.rx_impl.stop()
}
#[cfg(esp32s3)]
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
self.rx_impl.set_ext_mem_block_size(size);
}
#[cfg(gdma)]
fn set_mem2mem_mode(&mut self, value: bool) {
self.rx_impl.set_mem2mem_mode(value);
@ -2082,9 +2087,6 @@ pub trait Tx: crate::private::Sealed {
fn stop_transfer(&mut self);
#[cfg(esp32s3)]
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize);
fn is_done(&self) -> bool {
self.pending_out_interrupts()
.contains(DmaTxInterrupt::TotalEof)
@ -2200,11 +2202,17 @@ where
) -> Result<(), DmaError> {
debug_assert_eq!(preparation.direction, TransferDirection::Out);
#[cfg(esp32s3)]
if let Some(block_size) = preparation.external_memory_block_size {
self.set_ext_mem_block_size(block_size.into());
debug!("Preparing TX transfer {:?}", preparation);
trace!("First descriptor {:?}", unsafe { &*preparation.start });
#[cfg(psram_dma)]
if preparation.accesses_psram && !self.tx_impl.can_access_psram() {
return Err(DmaError::UnsupportedMemoryRegion);
}
#[cfg(psram_dma)]
self.tx_impl
.set_ext_mem_block_size(preparation.burst_transfer.external_memory.into());
self.tx_impl.set_burst_mode(preparation.burst_transfer);
self.tx_impl.set_descr_burst_mode(true);
self.tx_impl.set_check_owner(preparation.check_owner);
@ -2241,34 +2249,42 @@ where
) -> Result<(), DmaError> {
// Based on the ESP32-S3 TRM the alignment check is not needed for TX
// For esp32s3 we check each descriptor buffer that points to PSRAM for
// We check each descriptor buffer that points to PSRAM for
// alignment and writeback the cache for that buffer.
// Note that DmaBuffer implementations are required to do this for us.
#[cfg(esp32s3)]
for des in chain.descriptors.iter() {
// we are forcing the DMA alignment to the cache line size
// required when we are using dcache
let alignment = crate::soc::cache_get_dcache_line_size() as usize;
if crate::soc::is_valid_psram_address(des.buffer as usize) {
// both the size and address of the buffer must be aligned
if des.buffer as usize % alignment != 0 && des.size() % alignment != 0 {
return Err(DmaError::InvalidAlignment);
#[cfg(psram_dma)]
cfg_if::cfg_if! {
if #[cfg(psram_dma)] {
let mut uses_psram = false;
let psram_range = crate::soc::psram_range();
for des in chain.descriptors.iter() {
// we are forcing the DMA alignment to the cache line size
// required when we are using dcache
let alignment = crate::soc::cache_get_dcache_line_size() as usize;
if crate::soc::addr_in_range(des.buffer as usize, psram_range.clone()) {
uses_psram = true;
// both the size and address of the buffer must be aligned
if des.buffer as usize % alignment != 0 {
return Err(DmaError::InvalidAlignment(DmaAlignmentError::Address));
}
if des.size() % alignment != 0 {
return Err(DmaError::InvalidAlignment(DmaAlignmentError::Size));
}
crate::soc::cache_writeback_addr(des.buffer as u32, des.size() as u32);
}
}
crate::soc::cache_writeback_addr(des.buffer as u32, des.size() as u32);
}
}
self.do_prepare(
Preparation {
start: chain.first().cast_mut(),
#[cfg(esp32s3)]
external_memory_block_size: None,
direction: TransferDirection::Out,
burst_transfer: BurstConfig::Disabled,
check_owner: Some(false),
},
peri,
)?;
let preparation = Preparation {
start: chain.first().cast_mut(),
direction: TransferDirection::Out,
#[cfg(psram_dma)]
accesses_psram: uses_psram,
burst_transfer: BurstConfig::default(),
check_owner: Some(false),
};
self.do_prepare(preparation, peri)?;
// enable descriptor write back in circular mode
self.tx_impl
@ -2304,11 +2320,6 @@ where
self.tx_impl.stop()
}
#[cfg(esp32s3)]
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
self.tx_impl.set_ext_mem_block_size(size);
}
fn listen_out(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
self.tx_impl.listen(interrupts);
}
@ -2379,11 +2390,14 @@ pub trait RegisterAccess: crate::private::Sealed {
/// descriptor.
fn set_check_owner(&self, check_owner: Option<bool>);
#[cfg(esp32s3)]
#[cfg(psram_dma)]
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize);
#[cfg(pdma)]
fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool;
#[cfg(psram_dma)]
fn can_access_psram(&self) -> bool;
}
#[doc(hidden)]

View File

@ -122,6 +122,18 @@ impl RegisterAccess for AnySpiDmaTxChannel {
fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
self.0.is_compatible_with(peripheral)
}
#[cfg(psram_dma)]
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
let spi = self.0.register_block();
spi.dma_conf()
.modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
}
#[cfg(psram_dma)]
fn can_access_psram(&self) -> bool {
matches!(self.0, AnySpiDmaChannel(AnySpiDmaChannelInner::Spi2(_)))
}
}
impl TxRegisterAccess for AnySpiDmaTxChannel {
@ -280,6 +292,18 @@ impl RegisterAccess for AnySpiDmaRxChannel {
fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
self.0.is_compatible_with(peripheral)
}
#[cfg(psram_dma)]
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
let spi = self.0.register_block();
spi.dma_conf()
.modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
}
#[cfg(psram_dma)]
fn can_access_psram(&self) -> bool {
matches!(self.0, AnySpiDmaChannel(AnySpiDmaChannelInner::Spi2(_)))
}
}
impl RxRegisterAccess for AnySpiDmaRxChannel {
@ -475,6 +499,18 @@ impl RegisterAccess for AnyI2sDmaTxChannel {
fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
self.0.is_compatible_with(peripheral)
}
#[cfg(psram_dma)]
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
let spi = self.0.register_block();
spi.lc_conf()
.modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
}
#[cfg(psram_dma)]
fn can_access_psram(&self) -> bool {
matches!(self.0, AnyI2sDmaChannel(AnyI2sDmaChannelInner::I2s0(_)))
}
}
impl TxRegisterAccess for AnyI2sDmaTxChannel {
@ -645,6 +681,18 @@ impl RegisterAccess for AnyI2sDmaRxChannel {
fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
self.0.is_compatible_with(peripheral)
}
#[cfg(psram_dma)]
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
let spi = self.0.register_block();
spi.lc_conf()
.modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
}
#[cfg(psram_dma)]
fn can_access_psram(&self) -> bool {
matches!(self.0, AnyI2sDmaChannel(AnyI2sDmaChannelInner::I2s0(_)))
}
}
impl RxRegisterAccess for AnyI2sDmaRxChannel {

View File

@ -133,3 +133,33 @@ pub unsafe extern "C" fn ESP32Reset() -> ! {
pub extern "Rust" fn __init_data() -> bool {
false
}
/// Write back a specific range of data in the cache.
#[doc(hidden)]
#[link_section = ".rwtext"]
pub unsafe fn cache_writeback_addr(addr: u32, size: u32) {
extern "C" {
fn Cache_WriteBack_Addr(addr: u32, size: u32);
}
Cache_WriteBack_Addr(addr, size);
}
/// Invalidate a specific range of addresses in the cache.
#[doc(hidden)]
#[link_section = ".rwtext"]
pub unsafe fn cache_invalidate_addr(addr: u32, size: u32) {
extern "C" {
fn Cache_Invalidate_Addr(addr: u32, size: u32);
}
Cache_Invalidate_Addr(addr, size);
}
/// Get the size of a cache line in the DCache.
#[doc(hidden)]
#[link_section = ".rwtext"]
pub unsafe fn cache_get_dcache_line_size() -> u32 {
extern "C" {
fn Cache_Get_DCache_Line_Size() -> u32;
}
Cache_Get_DCache_Line_Size()
}

View File

@ -27,7 +27,7 @@ mod psram_common;
#[cfg(any(feature = "quad-psram", feature = "octal-psram"))]
static mut MAPPED_PSRAM: MappedPsram = MappedPsram { memory_range: 0..0 };
fn psram_range_internal() -> Range<usize> {
pub(crate) fn psram_range() -> Range<usize> {
cfg_if::cfg_if! {
if #[cfg(any(feature = "quad-psram", feature = "octal-psram"))] {
unsafe { MAPPED_PSRAM.memory_range.clone() }
@ -112,12 +112,12 @@ pub(crate) fn is_slice_in_dram<T>(slice: &[T]) -> bool {
#[allow(unused)]
pub(crate) fn is_valid_psram_address(address: usize) -> bool {
addr_in_range(address, psram_range_internal())
addr_in_range(address, psram_range())
}
#[allow(unused)]
pub(crate) fn is_slice_in_psram<T>(slice: &[T]) -> bool {
slice_in_range(slice, psram_range_internal())
slice_in_range(slice, psram_range())
}
#[allow(unused)]
@ -135,6 +135,6 @@ fn slice_in_range<T>(slice: &[T], range: Range<usize>) -> bool {
addr_in_range(start, range.clone()) && end <= range.end
}
fn addr_in_range(addr: usize, range: Range<usize>) -> bool {
pub(crate) fn addr_in_range(addr: usize, range: Range<usize>) -> bool {
range.contains(&addr)
}

View File

@ -1,5 +1,3 @@
use core::ops::Range;
/// Size of PSRAM
///
/// [PsramSize::AutoDetect] will try to detect the size of PSRAM
@ -26,15 +24,9 @@ impl PsramSize {
}
}
/// Returns the address range available in external memory.
#[cfg(any(feature = "quad-psram", feature = "octal-psram"))]
pub(crate) fn psram_range(_psram: &crate::peripherals::PSRAM) -> Range<usize> {
unsafe { super::MAPPED_PSRAM.memory_range.clone() }
}
/// Returns the address and size of the available in external memory.
#[cfg(any(feature = "quad-psram", feature = "octal-psram"))]
pub fn psram_raw_parts(psram: &crate::peripherals::PSRAM) -> (*mut u8, usize) {
let range = psram_range(psram);
pub fn psram_raw_parts(_psram: &crate::peripherals::PSRAM) -> (*mut u8, usize) {
let range = crate::soc::psram_range();
(range.start as *mut u8, range.end - range.start)
}

View File

@ -72,4 +72,7 @@ symbols = [
"uart_support_wakeup_int",
"ulp_supported",
"riscv_coproc_supported",
# Other capabilities
"psram_dma",
]

View File

@ -68,6 +68,7 @@ symbols = [
"bt",
"wifi",
"psram",
"psram_dma",
"octal_psram",
"ulp_riscv_core",
"timg_timer1",
@ -88,4 +89,7 @@ symbols = [
"uart_support_wakeup_int",
"ulp_supported",
"riscv_coproc_supported",
# Other capabilities
"psram_dma",
]

View File

@ -25,7 +25,7 @@
use esp_backtrace as _;
use esp_hal::{
delay::Delay,
dma::{DmaBufBlkSize, DmaRxBuf, DmaTxBuf},
dma::{DmaRxBuf, DmaTxBuf, ExternalBurstConfig},
peripheral::Peripheral,
prelude::*,
spi::{
@ -51,7 +51,7 @@ macro_rules! dma_alloc_buffer {
}
const DMA_BUFFER_SIZE: usize = 8192;
const DMA_ALIGNMENT: DmaBufBlkSize = DmaBufBlkSize::Size64;
const DMA_ALIGNMENT: ExternalBurstConfig = ExternalBurstConfig::Size64;
const DMA_CHUNK_SIZE: usize = 4096 - DMA_ALIGNMENT as usize;
#[entry]
@ -77,7 +77,7 @@ fn main() -> ! {
tx_descriptors.len()
);
let mut dma_tx_buf =
DmaTxBuf::new_with_block_size(tx_descriptors, tx_buffer, Some(DMA_ALIGNMENT)).unwrap();
DmaTxBuf::new_with_config(tx_descriptors, tx_buffer, DMA_ALIGNMENT).unwrap();
let (rx_buffer, rx_descriptors, _, _) = esp_hal::dma_buffers!(DMA_BUFFER_SIZE, 0);
info!(
"RX: {:p} len {} ({} descripters)",

View File

@ -184,7 +184,8 @@ mod tests {
#[test]
#[cfg(pcnt)]
fn test_dma_read_dma_write_pcnt(ctx: Context) {
const DMA_BUFFER_SIZE: usize = 5;
const DMA_BUFFER_SIZE: usize = 8;
const TRANSFER_SIZE: usize = 5;
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(DMA_BUFFER_SIZE);
let mut dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
let mut dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
@ -196,31 +197,35 @@ mod tests {
unit.channel0
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
dma_rx_buf.set_length(TRANSFER_SIZE);
dma_tx_buf.set_length(TRANSFER_SIZE);
// Fill the buffer where each byte has 3 pos edges.
dma_tx_buf.as_mut_slice().fill(0b0110_1010);
for i in 1..4 {
dma_rx_buf.as_mut_slice().copy_from_slice(&[5, 5, 5, 5, 5]);
dma_rx_buf.as_mut_slice()[..TRANSFER_SIZE].copy_from_slice(&[5; TRANSFER_SIZE]);
let transfer = spi
.read(dma_rx_buf.len(), dma_rx_buf)
.read(TRANSFER_SIZE, dma_rx_buf)
.map_err(|e| e.0)
.unwrap();
(spi, dma_rx_buf) = transfer.wait();
assert_eq!(dma_rx_buf.as_slice(), &[0, 0, 0, 0, 0]);
assert_eq!(&dma_rx_buf.as_slice()[..TRANSFER_SIZE], &[0; TRANSFER_SIZE]);
let transfer = spi
.write(dma_tx_buf.len(), dma_tx_buf)
.write(TRANSFER_SIZE, dma_tx_buf)
.map_err(|e| e.0)
.unwrap();
(spi, dma_tx_buf) = transfer.wait();
assert_eq!(unit.value(), (i * 3 * DMA_BUFFER_SIZE) as _);
assert_eq!(unit.value(), (i * 3 * TRANSFER_SIZE) as _);
}
}
#[test]
#[cfg(pcnt)]
fn test_dma_read_dma_transfer_pcnt(ctx: Context) {
const DMA_BUFFER_SIZE: usize = 5;
const DMA_BUFFER_SIZE: usize = 8;
const TRANSFER_SIZE: usize = 5;
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(DMA_BUFFER_SIZE);
let mut dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
let mut dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
@ -232,24 +237,27 @@ mod tests {
unit.channel0
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
dma_rx_buf.set_length(TRANSFER_SIZE);
dma_tx_buf.set_length(TRANSFER_SIZE);
// Fill the buffer where each byte has 3 pos edges.
dma_tx_buf.as_mut_slice().fill(0b0110_1010);
for i in 1..4 {
dma_rx_buf.as_mut_slice().copy_from_slice(&[5, 5, 5, 5, 5]);
dma_rx_buf.as_mut_slice()[..TRANSFER_SIZE].copy_from_slice(&[5; TRANSFER_SIZE]);
let transfer = spi
.read(dma_rx_buf.len(), dma_rx_buf)
.read(TRANSFER_SIZE, dma_rx_buf)
.map_err(|e| e.0)
.unwrap();
(spi, dma_rx_buf) = transfer.wait();
assert_eq!(dma_rx_buf.as_slice(), &[0, 0, 0, 0, 0]);
assert_eq!(&dma_rx_buf.as_slice()[..TRANSFER_SIZE], &[0; TRANSFER_SIZE]);
let transfer = spi
.transfer(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf)
.transfer(TRANSFER_SIZE, dma_rx_buf, TRANSFER_SIZE, dma_tx_buf)
.map_err(|e| e.0)
.unwrap();
(spi, (dma_rx_buf, dma_tx_buf)) = transfer.wait();
assert_eq!(unit.value(), (i * 3 * DMA_BUFFER_SIZE) as _);
assert_eq!(unit.value(), (i * 3 * TRANSFER_SIZE) as _);
}
}
@ -285,7 +293,9 @@ mod tests {
#[test]
fn test_asymmetric_dma_transfer(ctx: Context) {
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(2, 4);
const WRITE_SIZE: usize = 4;
const READ_SIZE: usize = 2;
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(4, 4);
let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
let mut dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
@ -293,22 +303,28 @@ mod tests {
let spi = ctx.spi.with_dma(ctx.dma_channel);
let transfer = spi
.transfer(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf)
.transfer(READ_SIZE, dma_rx_buf, WRITE_SIZE, dma_tx_buf)
.map_err(|e| e.0)
.unwrap();
let (spi, (dma_rx_buf, mut dma_tx_buf)) = transfer.wait();
assert_eq!(dma_tx_buf.as_slice()[0..2], dma_rx_buf.as_slice()[0..2]);
assert_eq!(
dma_tx_buf.as_slice()[0..READ_SIZE],
dma_rx_buf.as_slice()[0..READ_SIZE]
);
// Try transfer again to make sure DMA isn't in a broken state.
dma_tx_buf.fill(&[0xaa, 0xdd, 0xef, 0xbe]);
let transfer = spi
.transfer(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf)
.transfer(READ_SIZE, dma_rx_buf, WRITE_SIZE, dma_tx_buf)
.map_err(|e| e.0)
.unwrap();
let (_, (dma_rx_buf, dma_tx_buf)) = transfer.wait();
assert_eq!(dma_tx_buf.as_slice()[0..2], dma_rx_buf.as_slice()[0..2]);
assert_eq!(
dma_tx_buf.as_slice()[0..READ_SIZE],
dma_rx_buf.as_slice()[0..READ_SIZE]
);
}
#[test]
@ -373,7 +389,8 @@ mod tests {
#[test]
#[cfg(pcnt)]
async fn test_async_dma_read_dma_write_pcnt(ctx: Context) {
const DMA_BUFFER_SIZE: usize = 5;
const DMA_BUFFER_SIZE: usize = 8;
const TRANSFER_SIZE: usize = 5;
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(DMA_BUFFER_SIZE);
let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
@ -388,25 +405,26 @@ mod tests {
.channel0
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
let mut receive = [0; DMA_BUFFER_SIZE];
let mut receive = [0; TRANSFER_SIZE];
// Fill the buffer where each byte has 3 pos edges.
let transmit = [0b0110_1010; DMA_BUFFER_SIZE];
let transmit = [0b0110_1010; TRANSFER_SIZE];
for i in 1..4 {
receive.copy_from_slice(&[5, 5, 5, 5, 5]);
receive.copy_from_slice(&[5; TRANSFER_SIZE]);
SpiBusAsync::read(&mut spi, &mut receive).await.unwrap();
assert_eq!(receive, [0, 0, 0, 0, 0]);
assert_eq!(receive, [0; TRANSFER_SIZE]);
SpiBusAsync::write(&mut spi, &transmit).await.unwrap();
assert_eq!(ctx.pcnt_unit.value(), (i * 3 * DMA_BUFFER_SIZE) as _);
assert_eq!(ctx.pcnt_unit.value(), (i * 3 * TRANSFER_SIZE) as _);
}
}
#[test]
#[cfg(pcnt)]
async fn test_async_dma_read_dma_transfer_pcnt(ctx: Context) {
const DMA_BUFFER_SIZE: usize = 5;
const DMA_BUFFER_SIZE: usize = 8;
const TRANSFER_SIZE: usize = 5;
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(DMA_BUFFER_SIZE);
let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
@ -421,10 +439,10 @@ mod tests {
.channel0
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
let mut receive = [0; DMA_BUFFER_SIZE];
let mut receive = [0; TRANSFER_SIZE];
// Fill the buffer where each byte has 3 pos edges.
let transmit = [0b0110_1010; DMA_BUFFER_SIZE];
let transmit = [0b0110_1010; TRANSFER_SIZE];
for i in 1..4 {
receive.copy_from_slice(&[5, 5, 5, 5, 5]);
@ -434,7 +452,7 @@ mod tests {
SpiBusAsync::transfer(&mut spi, &mut receive, &transmit)
.await
.unwrap();
assert_eq!(ctx.pcnt_unit.value(), (i * 3 * DMA_BUFFER_SIZE) as _);
assert_eq!(ctx.pcnt_unit.value(), (i * 3 * TRANSFER_SIZE) as _);
}
}

View File

@ -7,7 +7,7 @@
use defmt::error;
use esp_alloc as _;
use esp_hal::{
dma::{DmaBufBlkSize, DmaRxBuf, DmaTxBuf},
dma::{DmaRxBuf, DmaTxBuf, ExternalBurstConfig},
dma_buffers,
dma_descriptors_chunk_size,
gpio::interconnect::InputSignal,
@ -83,13 +83,12 @@ mod tests {
#[test]
fn test_spi_writes_are_correctly_by_pcnt(ctx: Context) {
const DMA_BUFFER_SIZE: usize = 4;
const DMA_ALIGNMENT: DmaBufBlkSize = DmaBufBlkSize::Size32;
const DMA_ALIGNMENT: ExternalBurstConfig = ExternalBurstConfig::Size32;
const DMA_CHUNK_SIZE: usize = 4096 - DMA_ALIGNMENT as usize;
let (_, descriptors) = dma_descriptors_chunk_size!(0, DMA_BUFFER_SIZE, DMA_CHUNK_SIZE);
let buffer = dma_alloc_buffer!(DMA_BUFFER_SIZE, DMA_ALIGNMENT as usize);
let mut dma_tx_buf =
DmaTxBuf::new_with_block_size(descriptors, buffer, Some(DMA_ALIGNMENT)).unwrap();
let mut dma_tx_buf = DmaTxBuf::new_with_config(descriptors, buffer, DMA_ALIGNMENT).unwrap();
let unit = ctx.pcnt_unit;
let mut spi = ctx.spi;
@ -134,13 +133,12 @@ mod tests {
#[test]
fn test_spidmabus_writes_are_correctly_by_pcnt(ctx: Context) {
const DMA_BUFFER_SIZE: usize = 4;
const DMA_ALIGNMENT: DmaBufBlkSize = DmaBufBlkSize::Size32; // matches dcache line size
const DMA_ALIGNMENT: ExternalBurstConfig = ExternalBurstConfig::Size32; // matches dcache line size
const DMA_CHUNK_SIZE: usize = 4096 - DMA_ALIGNMENT as usize; // 64 byte aligned
let (_, descriptors) = dma_descriptors_chunk_size!(0, DMA_BUFFER_SIZE, DMA_CHUNK_SIZE);
let buffer = dma_alloc_buffer!(DMA_BUFFER_SIZE, DMA_ALIGNMENT as usize);
let dma_tx_buf =
DmaTxBuf::new_with_block_size(descriptors, buffer, Some(DMA_ALIGNMENT)).unwrap();
let dma_tx_buf = DmaTxBuf::new_with_config(descriptors, buffer, DMA_ALIGNMENT).unwrap();
let (rx, rxd, _, _) = dma_buffers!(1, 0);
let dma_rx_buf = DmaRxBuf::new(rxd, rx).unwrap();