ARM

Understanding Cortex-M7 Cache Coherency in Multi-DMA Systems

Introduction

La cohérence de cache est l’un des problèmes les plus subtils dans les systèmes embarqués haute performance. Sur le STM32H7, le Cortex-M7 dispose d’un D-cache de 16 KB qui peut entraîner des comportements inattendus lorsque le DMA accède à la mémoire en parallèle avec le CPU.

Le problème fondamental

Le D-cache du Cortex-M7 fonctionne en mode write-back par défaut. Les écritures du CPU sont stockées dans le cache puis propagées vers la RAM de manière différée. Quand un contrôleur DMA lit depuis cette même zone RAM, il peut lire des données obsolètes.

/* Problème typique — envoi SPI avec DMA */
uint8_t tx_buffer[64] __attribute__((aligned(32)));

/* CPU écrit dans tx_buffer — données dans le cache */
memcpy(tx_buffer, new_data, 64);

/* DMA lit depuis la RAM — données OBSOLÈTES ! */
HAL_SPI_Transmit_DMA(&hspi1, tx_buffer, 64);

Solution 1 — Cache Clean avant transfer DMA (CPU → périphérique)

Avant de lancer un transfer DMA depuis un buffer CPU, forcez l’écriture du cache vers la RAM :

/* Prépare les données */
memcpy(tx_buffer, new_data, 64);

/* Clean : force l'écriture cache → RAM */
SCB_CleanDCache_by_Addr(
    (uint32_t *)tx_buffer,
    sizeof(tx_buffer)
);

/* DMA lit maintenant des données fraîches */
HAL_SPI_Transmit_DMA(&hspi1, tx_buffer, 64);

Solution 2 — Cache Invalidate après transfer DMA (périphérique → CPU)

Pour les transfers entrants, invalidez le cache après la fin du transfer :

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) {
    /* Invalide : force le CPU à relire depuis la RAM */
    SCB_InvalidateDCache_by_Addr(
        (uint32_t *)rx_buffer,
        sizeof(rx_buffer)
    );
    process_data(rx_buffer);
}

Solution 3 — MPU avec région Non-Cacheable

La solution la plus robuste pour les buffers DMA fréquents :

void MPU_Config(void) {
    MPU_Region_InitTypeDef MPU_InitStruct = {0};
    HAL_MPU_Disable();

    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000; /* AXI SRAM */
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

Règles d’or

  • DMA TX : SCB_CleanDCache_by_Addr() avant de lancer le DMA
  • DMA RX : SCB_InvalidateDCache_by_Addr() dans le callback de fin
  • Alignement : buffers DMA alignés sur 32 bytes obligatoire
  • MPU : marquer la région Non-Cacheable pour les buffers très fréquents

Conclusion

La cohérence de cache sur Cortex-M7 est une contrainte architecturale à intégrer dès la conception. Une bonne stratégie est d’allouer tous les buffers DMA dans une région MPU dédiée non-cacheable dès le début du projet.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *