mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-27 12:20:56 +00:00
[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:
parent
2d4ccd735f
commit
9458fd3ed4
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
|
@ -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)]
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -72,4 +72,7 @@ symbols = [
|
||||
"uart_support_wakeup_int",
|
||||
"ulp_supported",
|
||||
"riscv_coproc_supported",
|
||||
|
||||
# Other capabilities
|
||||
"psram_dma",
|
||||
]
|
||||
|
@ -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",
|
||||
]
|
||||
|
@ -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)",
|
||||
|
@ -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 _);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user