Skip to content

sdmmc_sdio_hal: two Cortex-M7 cache coherency defects in SD DMA read path (safe on single-core, risk on future multi-core targets) #11562

@daijoubu

Description

@daijoubu

Summary

Two memory-ordering defects exist in src/main/drivers/sdcard/sdmmc_sdio_hal.c in the DMA read path. Both are safe in practice on all current single-core INAV targets (STM32F4/F7/H7), but would become genuine race conditions on any future multi-core target such as the RP2350 (dual Cortex-M33).

The same code is present verbatim in Betaflight (src/platform/STM32/sdio_h7xx.c).


Finding 1 — sdReadParameters missing volatile

Location: sdmmc_sdio_hal.c, near line 633

// current
sdReadParameters_t sdReadParameters;

// correct
static volatile sdReadParameters_t sdReadParameters;

sdReadParameters holds the DMA receive buffer pointer, block size, and block count. It is written by SD_ReadBlocks_DMA() (main context) and read by HAL_SD_RxCpltCallback() (ISR context) to calculate the D-cache invalidation range. Without volatile, the C standard permits the compiler to cache these fields in registers and not reload from memory inside the ISR, meaning cache invalidation could use a stale buffer address or wrong size.

In practice GCC flushes globals to memory at external function call boundaries, so this does not currently manifest. On a multi-core target a second core running the ISR would have no such guarantee.

The variable also has unnecessary external linkage; making it static is a clean-up with no functional impact.


Finding 2 — RXCplt completion flag cleared before D-cache invalidation

Location: sdmmc_sdio_hal.c, HAL_SD_RxCpltCallback()

// current — semantically wrong ordering
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
    SD_Handle.RXCplt = 0;                        // signals "done" ...
    uint32_t alignedAddr = ...;
    SCB_InvalidateDCache_by_Addr(...);            // ... before cache is coherent
}

// correct — invalidate first, then signal done
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
    uint32_t alignedAddr = ...;
    SCB_InvalidateDCache_by_Addr(...);
    __DMB();                                     // drain before flag store
    SD_Handle.RXCplt = 0;
}

The ready flag RXCplt = 0 is stored before SCB_InvalidateDCache_by_Addr() runs. The main loop polls SD_CheckRead() which reads RXCplt; once it observes zero it immediately accesses the receive buffer. If RXCplt = 0 becomes visible to another core before the cache invalidation completes, that core reads stale cached data.

On current single-core targets the ISR runs to completion before the main loop resumes, and SCB_InvalidateDCache_by_Addr contains an internal __DSB() that incidentally drains the write buffer anyway — so the ordering bug has no observable effect. On a multi-core target it is a real data race.

The fix mirrors the store-release pattern established for the CAN TX ISR SPSC queue (DMB between data and flag store).


Affected code paths

The read path is actively used: asyncfatfs calls sdcard_readBlock() to read FAT sectors (boot sector, FAT tables, directory entries) whenever it needs a sector not in its cache. This occurs at every SD card mount and throughout blackbox logging sessions.

Affected targets: all 20 targets defining USE_SDCARD_SDIO, including MATEKF765, MATEKH743, KAKUTEH7WING, and others.


Proposed fix

-sdReadParameters_t sdReadParameters;
+static volatile sdReadParameters_t sdReadParameters;
 void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
 {
     UNUSED(hsd);
 
-    SD_Handle.RXCplt = 0;
-
     /*
        the SCB_InvalidateDCache_by_Addr() requires a 32-Byte aligned address,
        adjust the address and the D-Cache size to invalidate accordingly.
+       Invalidate before clearing RXCplt so the buffer is coherent by the
+       time the main loop observes the transfer as complete.
      */
-    uint32_t alignedAddr = (uint32_t)sdReadParameters.buffer &  ~0x1F;
+    uint32_t alignedAddr = (uint32_t)sdReadParameters.buffer & ~0x1F;
     SCB_InvalidateDCache_by_Addr((uint32_t*)alignedAddr, sdReadParameters.NumberOfBlocks * sdReadParameters.BlockSize + ((uint32_t)sdReadParameters.buffer - alignedAddr));
+    __DMB();
+    SD_Handle.RXCplt = 0;
 }

The same fix should be applied to Betaflight's src/platform/STM32/sdio_h7xx.c.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions