Skip to main content
ST Employee
August 10, 2026

How to maintain DCACHE coherency for DMA on STM32MP2xx Cortex®-M33

  • August 10, 2026
  • 5 replies
  • 93 views

Summary

This article explains how to maintain data cache coherency on STM32MP2xx Cortex®-M33 when DCACHE is enabled and DMA shares buffers with the CPU. It explains when DCACHE maintenance is required, how the requirement depends on the memory protection unit (MPU) and linker memory layout. Finally, how to use the STM32MP2xx hardware abstraction layer (HAL) APIs in a typical DMA transfer.

Introduction

This article is intended for embedded software engineers who use STM32MP2xx Cortex®-M33 with DMA and DCACHE enabled.

DCACHE improves performance, but the CPU view of a buffer can differ from the memory view that DMA uses. If DCACHE maintenance is missing, DMA can read stale data from memory, or the CPU can read stale data from the cache after DMA completes.In some cases, a dirty cache line can also be written back to memory during an inbound DMA transfer and overwrite data just written by DMA.

In addition, cache maintenance operates on cache lines, not on individual bytes. Therefore, DMA buffers located in cacheable memory must also be designed with cache-line alignment in mind.

1. Understand the DCACHE coherency policy

On STM32MP2xx Cortex®-M33, DMA coherency depends on two factors:

  • Whether the buffer is in a cacheable or noncacheable region.
  • Whether the CPU or DMA updates the buffer first.

DCACHE maintenance is required only for cacheable regions. Accesses to noncacheable regions bypass the cache and do not require clean or invalidate operations.

In practice, the coherency policy is as follows:

  • If a shared buffer is in a noncacheable region, no DCACHE maintenance is required.
  • If a shared buffer is in a cacheable region, software must maintain coherency explicitly.

For cacheable DMA buffers, apply the following rules:

  • CPU writes to the buffer, then DMA reads the buffer: clean.
  • DMA writes to the buffer, then the CPU reads the buffer: invalidate before DMA start and invalidate again after DMA completion.

This usage aligns with the HAL driver notes:

  • HAL_DCACHE_CleanByAddr() is used when the CPU updates a buffer before a peripheral such as DMA uses it.
  • HAL_DCACHE_InvalidateByAddr() is used when a peripheral such as DMA updates a buffer before the CPU reads it.

Why two invalidates are needed for DMA-written buffers

          For a cacheable destination buffer, the two invalidates serve different purposes:

  • Invalidate before DMA start: prevents any dirty cache line covering the destination buffer from being evicted during the DMA transfer and writing stale CPU data back to memory.
  • Invalidate after DMA completion: removes any stale cache lines so the CPU fetches the data updated by DMA from memory.

So for an inbound DMA transfer into a cacheable buffer, the correct rule is:

  • invalidate before DMA
  • invalidate after DMA

2. Check whether the buffer is in a cacheable region

DCACHE maintenance applies only to cacheable memory regions.

In the provided MPU configuration, the application defines one cacheable region and one noncacheable region.

Example of cacheable MPU attributes:

/* write back, read and write allocate */
MPU_Attributes_InitStruct.Attributes = INNER_OUTER(MPU_WRITE_BACK | MPU_NON_TRANSIENT | MPU_RW_ALLOCATE);
MPU_Attributes_InitStruct.Number = MPU_ATTRIBUTES_NUMBER0;
HAL_MPU_ConfigMemoryAttributes(&MPU_Attributes_InitStruct);
/* DCACHE */
MPU_InitStruct.AccessPermission = MPU_REGION_ALL_RW;
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.AttributesIndex = MPU_ATTRIBUTES_NUMBER0;
MPU_InitStruct.DisableExec= MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_INNER_SHAREABLE;
MPU_InitStruct.BaseAddress = 0x80A00000;
MPU_InitStruct.LimitAddress = 0x80DFFC00;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

Example of noncacheable MPU attributes:

MPU_Attributes_InitStruct.Attributes = INNER_OUTER(MPU_NOT_CACHEABLE);
MPU_Attributes_InitStruct.Number = MPU_ATTRIBUTES_NUMBER1;
HAL_MPU_ConfigMemoryAttributes(&MPU_Attributes_InitStruct);

MPU_InitStruct.AccessPermission = MPU_REGION_ALL_RW;
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.AttributesIndex = MPU_ATTRIBUTES_NUMBER1;
MPU_InitStruct.DisableExec= MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_INNER_SHAREABLE;
MPU_InitStruct.BaseAddress = 0x81200000;
MPU_InitStruct.LimitAddress = 0x812FFFFF;
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

The linker file defines the related memory regions as follows:

MEMORY
{
NS_VECTOR_TBL (xrw) : ORIGIN = 0x80100000, LENGTH = 0x00000600
RAM (rx) : ORIGIN = 0x80A00000, LENGTH = 4M
IPC_SHMEM_1 (rw) : ORIGIN = 0x81200000, LENGTH = 1M - _virtio_shmem_size
VIRTIO_SHMEM (rw) : ORIGIN = ORIGIN(IPC_SHMEM_1)+LENGTH(IPC_SHMEM_1), LENGTH = _virtio_shmem_size

}

 

This configuration means that:

  • RAM at 0x80A00000 is in the cacheable DCACHE region.
  • IPC_SHMEM_1 and VIRTIO_SHMEM at 0x81200000 are in the noncacheable region.

2.1 Example from this project

Normal application buffers are typically placed in .data or .bss. In the linker file, both sections are mapped to RAM:

/* Initialized data sections into "RAM" Ram type memory */
.data : ALIGN(LINKER_DEFAULT_ALIGNMENT)
{
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */

_edata = .; /* define a global symbol at data end */
. = ALIGN(LINKER_DEFAULT_ALIGNMENT);
} >RAM

/* Uninitialized data section into "RAM" Ram type memory */
.bss : ALIGN(LINKER_DEFAULT_ALIGNMENT)
{
/* This is used by the startup in order to initialize the .bss section */
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)

_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
. = ALIGN(LINKER_DEFAULT_ALIGNMENT);
} >RAM

As a result, buffers such as:

__attribute__((aligned(32))) uint32_t aSRC_Buffer1[BUFFER1_SIZE]; 

__attribute__((aligned(32))) uint32_t aDST_Buffer1[BUFFER1_SIZE];

are placed in RAM, which is cacheable in this project. When DMA shares these buffers, DCACHE maintenance is required.

By contrast, IPC_SHMEM_1 and VIRTIO_SHMEM are already defined as shared memory regions and are mapped by the MPU as noncacheable. For these regions, DCACHE maintenance is not required.

Important: if a DMA buffer is placed in a cacheable region, software must also ensure that the buffer does not share cache lines with unrelated data. This is typically achieved by using cache-line aligned buffers and a buffer size, or maintained range, that covers the full cache-line (32 bytes) envelope.

3. Use the relevant HAL APIs

The STM32MP2xx HAL provides the following APIs:

  • HAL_DCACHE_CleanByAddr(): writes back dirty cache lines for an address range to memory.
  • HAL_DCACHE_InvalidateByAddr(): invalidates cache lines for an address range.
  • HAL_DCACHE_CleanInvalidByAddr(): writes back and invalidates cache lines for an address range.

For DMA coherency, the main functions  are:

HAL_DCACHE_CleanByAddr() and HAL_DCACHE_InvalidateByAddr().

3.1 HAL_DCACHE_CleanByAddr()

Use this function when the CPU updates a buffer and DMA must read it.

HAL_DCACHE_CleanByAddr(DCACHE_HandleTypeDef *hdcache, const uint32_t *const pAddr, uint32_t dSize)

Parameters:

  • hdcache: pointer to the DCACHE handle.
  • pAddr: start address of the region to clean.
  • dSize: size of the region to clean, in bytes.

3.2 HAL_DCACHE_InvalidateByAddr()

Use this function when DMA updates a buffer and the CPU must read it.

HAL_DCACHE_InvalidateByAddr(DCACHE_HandleTypeDef *hdcache, const uint32_t *const pAddr, uint32_t dSize);

Parameters:

  • hdcache: pointer to the DCACHE handle.
  • pAddr: start address of the region to invalidate.
  • dSize: size of the region to invalidate, in bytes.

Note about address-based cache maintenance

These APIs operate on cache lines, not on individual bytes. Therefore, when using them on DMA buffers in cacheable memory:

  • the buffer start address should be aligned to the cache-line size
  • the maintained size, or maintained envelope, should cover whole cache lines
  • Otherwise, cache maintenance may affect adjacent data sharing the same cache line.

4. Clean DCACHE before DMA reads a CPU-updated buffer

If the CPU prepares a source buffer and DMA reads it, clean the corresponding cache lines before DMA starts. This function writes back dirty cache lines to memory so that DMA reads the latest data. In the STM32MP2xx DMA linked-list example, the source buffers are cleaned before the transfer starts:

 /* Flushing the cache before DMA transfer */
HAL_DCACHE_CleanByAddr(&hdcache, aSRC_Buffer1, BUFFER1_SIZE* sizeof(uint32_t));
HAL_DCACHE_CleanByAddr(&hdcache, aSRC_Buffer2, BUFFER2_SIZE* sizeof(uint32_t));
HAL_DCACHE_CleanByAddr(&hdcache, aSRC_Buffer3, BUFFER3_SIZE* sizeof(uint32_t));
/* Configure the source, destination and buffer size DMA fields and Start DMA Channel/Stream transfer */
/* Enable All the DMA interrupts */
if (HAL_DMAEx_List_Start_IT(&handle_HPDMA3_Channel13) != HAL_OK)
{
Error_Handler();
}

If this step is skipped, DMA can transfer outdated buffer content.

5. Invalidate DCACHE before and after DMA writes a buffer

If DMA writes a destination buffer and the CPU reads it afterward, invalidate the corresponding cache lines after DMA completes. This function removes stale cached data so that the CPU reloads the updated content from memory.

In the STM32MP2xx DMA linked-list example, the destination buffers are invalidated in the transfer-complete callback:

/* Invalidate destination buffers before DMA writes them */
HAL_DCACHE_InvalidateByAddr(&hdcache, aDST_Buffer1, BUFFER1_SIZE * sizeof(uint32_t));
HAL_DCACHE_InvalidateByAddr(&hdcache, aDST_Buffer2, BUFFER2_SIZE * sizeof(uint32_t));
HAL_DCACHE_InvalidateByAddr(&hdcache, aDST_Buffer3, BUFFER3_SIZE * sizeof(uint32_t));

/* Configure the source, destination and buffer size DMA fields and Start DMA Channel/Stream transfer */
/* Enable All the DMA interrupts */
if (HAL_DMAEx_List_Start_IT(&handle_HPDMA3_Channel13) != HAL_OK)
{
Error_Handler();
}

/* Wait for end of transmission or an error occurred */
while ((TransferCompleteDetected == 0) && (TransferErrorDetected == 0U));

/* Check DMA error */
if (TransferErrorDetected == 1U)
{
Error_Handler();
}

/* Check destination buffer1 */
if (Buffercmp((uint8_t*)aSRC_Buffer1, (uint8_t*)aDST_Buffer1, (BUFFER1_SIZE * 4U)) != 0U)
{
Error_Handler();
}

/* Check destination buffer2 */
if (Buffercmp((uint8_t*)aSRC_Buffer2, (uint8_t*)aDST_Buffer2, (BUFFER2_SIZE * 4U)) != 0U)
{
Error_Handler();
}

/* Check destination buffer3 */
if (Buffercmp((uint8_t*)aSRC_Buffer3, (uint8_t*)aDST_Buffer3, (BUFFER3_SIZE * 4U)) != 0U)
{
Error_Handler();
}
static void TransferComplete(DMA_HandleTypeDef *hdma)
{
/* Invalidating cache lines after DMA transfer */
HAL_DCACHE_InvalidateByAddr(&hdcache, aDST_Buffer1, BUFFER1_SIZE * sizeof(uint32_t));
HAL_DCACHE_InvalidateByAddr(&hdcache, aDST_Buffer2, BUFFER2_SIZE * sizeof(uint32_t));
HAL_DCACHE_InvalidateByAddr(&hdcache, aDST_Buffer3, BUFFER3_SIZE * sizeof(uint32_t));
TransferCompleteDetected = 1U;
}

In a memory-to-memory DMA example, this sequence means:

  • Clean the source buffer before DMA starts.
  • Invalidate the destination buffer after DMA completes.

The following diagram summarizes the recommended DCACHE maintenance sequence for a DMA transfer using cacheable buffers on STM32MP2xx Cortex®-M33.

Figure 1. Recommended DCACHE maintenance sequence for DMA coherency.

6. Common mistakes to avoid

Avoid the following common mistakes:

  • Forgetting to clean a source buffer before DMA reads it.
  • Forgetting to invalidate a destination buffer before DMA starts when the destination is cacheable
  • Forgetting to invalidate a destination buffer after DMA completes before the CPU reads it
  • Assuming that a buffer in a noncacheable region still needs DCACHE maintenance.
  • Using DMA buffers that are not aligned to cache-line boundaries.

Conclusion

On STM32MP2xx Cortex®-M33, DCACHE coherency for DMA follows a simple policy:

  • Buffers in noncacheable regions do not require DCACHE maintenance.
  • Buffers in cacheable regions require explicit clean or invalidate operations.

For information about how to enable and disable caches on STM32MP2xx CM33, refer to the ST wiki:

ST wiki: How to add ICACHE and DCACHE support to your applications

5 replies

David Littell
Senior II
August 10, 2026

The article has generally good recommendations and explanations but there are some additional hard-won nuggets that can help to avoid some pretty miserable debugging experiences.  Got the scars...

Under “3. DMA transfer” above I believe there should be an invalidate step prior to starting an inbound DMA.  Without this it’s possible that a (dirty) cache line falling in the DMA buffer could be evicted during the DMA, possibly overwriting DMA-written data.  The invalidate following the completion of the DMA (4) is still necessary to ensure that all cache lines are in their proper state before the CPU attempts to read any data (5).

Also, the OP doesn’t mention the importance of cache-line alignment of the DMA buffers (both outbound and inbound) to avoid even more subtle problems related to the fact that the cache operations operation on complete lines, not bytes within a line.  (I also advocate sizing DMA buffer envelopes to a cache-line boundary in addition to the alignment criteria (for the same reason).  Some disagree with the need for the sizing criteria and I’m sure they’ll be along shortly...)

shivam203Author
ST Employee
August 11, 2026

Hi ​@David Littell ,
Thank you for reading my article and giving the feedback. You are correct: for a cacheable DMA destination buffer, an invalidate should also be performed before starting the DMA transfer, not only after completion. For the second part, I purposely not mentioned the cache alignment because it is more of a generic part for any buffer which is cacheable to be aligned whether we are talking about cache maintenance or not. It is a very important step for good cache maintenance. So, I updated the article on cache-line alignment as well.
Since cache maintenance operations apply to whole cache lines, DMA buffers should be cache-line aligned, and the maintained range should cover the full cache-line envelope of the buffer to avoid affecting adjacent unrelated data sharing the same cache line.

David Littell
Senior II
August 11, 2026

One more step for completeness: beyond the DMA data buffers themselves it might be helpful to discuss the more-sophisticated in-memory DMA-referenced control structures (e.g.,  linked lists) in the same light.  Because DMA references those types of structures just as it does outbound data buffers they should be handled in a similar fashion (cache line flushing, alignment, and sizing), yes?  And if the in-memory DMA control structures are updated by DMA as they’re processed (completion status, etc.) the invalidate-before-read pops up again.