From e892a141e966064b6f555ff4f6393424cce3459b Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 4 Jun 2026 15:45:56 +1000 Subject: [PATCH 1/2] tests: Rewrite stm32 sdcard_dma_align test to work with machine.SDCard. Now runs on mimxrt as well (less effective due to virtual timers only). Can be easily extended to run on other ports. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .../sdcard_dma_align.py | 67 +++++++++++++++---- 1 file changed, 53 insertions(+), 14 deletions(-) rename tests/{ports/stm32_hardware => extmod_hardware}/sdcard_dma_align.py (77%) diff --git a/tests/ports/stm32_hardware/sdcard_dma_align.py b/tests/extmod_hardware/sdcard_dma_align.py similarity index 77% rename from tests/ports/stm32_hardware/sdcard_dma_align.py rename to tests/extmod_hardware/sdcard_dma_align.py index 4af1ef543e49e..65902251e584c 100644 --- a/tests/ports/stm32_hardware/sdcard_dma_align.py +++ b/tests/extmod_hardware/sdcard_dma_align.py @@ -1,30 +1,44 @@ # Test DMA read operations when the buffer alignment in RAM varies. # +# Runs on both pyb.SDCard and machine.SDCard with default arguments, +# although some port-specific info is needed (see below) +# # Test requirements: -# A mostly empty FAT formatted SDCard installed in SD socket +# A mostly empty FAT formatted SDCard installed in SD socket (a card with other +# files may be much slower to start the test.) import errno import os -import pyb import machine import micropython +import sys import vfs import unittest from micropython import const +# Use pyb classes on stm32, machine class otherwise +try: + from pyb import SDCard, Timer +except ImportError: + try: + from machine import SDCard, Timer + except ImportError: + print("SKIP") + raise SystemExit + +READBLOCKS_SUCCESS = (0, True) # some drivers return True for success, some return 0... + _BLOCK_SZ = const(512) _OFFS_WIDTH = const(64) # Should be at least the cache line size plus the GC block size _TEST_BUF_SZ = const(_BLOCK_SZ + _OFFS_WIDTH) -# More repeats = longer test run, more chance of triggering a cache coherence issue -_REPEATS = const(8) - MOUNT_POINT = "/sd" FILE_PATH = "/sd/stm32_align.blk" # Skip the whole test if there isn't a mountable SDCard try: - sd = pyb.SDCard() + sd = SDCard() + fs = vfs.VfsFat(sd) vfs.mount(sd, MOUNT_POINT) vfs.umount(MOUNT_POINT) del sd @@ -33,6 +47,26 @@ raise SystemExit +# Set some port-specific parameters for test repeats & interrupt frequency +# (ports which can issue interrupts at a high frequency don't need to run as many +# repeats of the test in order to trigger a failure due to cache bugs.) +if "pyboard" in sys.platform: + + def make_timer(timer_cb): + # Pyboard SF6 can do at least this frequency and still run the test, + # possibly as high as 40kHz depending on the callback details. + return Timer(1, freq=35_000, callback=timer_cb, hard=True) + + _REPEATS = 8 +elif "mimxrt" in sys.platform: + + def make_timer(timer_cb): + return Timer(-1, period=1, callback=timer_cb, hard=True) + + # virtual timer limited to 1kHz so run more iterations (slow test!) + _REPEATS = 64 + + def verify_contents(buf, silent=False): # Verify that each byte in 'buf' has the value of its index in the buffer bad_pos = [] @@ -57,7 +91,7 @@ def verify_contents(buf, silent=False): class TestSDAlign(unittest.TestCase): @classmethod def setUpClass(cls): - cls.sd = pyb.SDCard() + cls.sd = SDCard() vfs.mount(cls.sd, MOUNT_POINT) buf = bytearray(_BLOCK_SZ) try: @@ -77,9 +111,11 @@ def setUpClass(cls): # Now look for the sector which holds the temporary file # (assume is in the first 20MB of the SD Card) + vfs.umount(MOUNT_POINT) for b in range(1, 40960): - if not cls.sd.readblocks(b, buf): - raise RuntimeError("Failed to call readblocks on SDCard. block:", b) + res = cls.sd.readblocks(b, buf) + if res not in READBLOCKS_SUCCESS: + raise RuntimeError("Failed to call readblocks on SDCard:", res, "block:", b) if verify_contents(buf, True): print("Temporary file contents found in block {}".format(b)) cls.block = b @@ -96,7 +132,10 @@ def tearDownClass(cls): print("Deleted temp file") except OSError: pass - vfs.umount(MOUNT_POINT) + try: + vfs.umount(MOUNT_POINT) + except OSError: + pass del cls.sd def setUp(self): @@ -110,8 +149,9 @@ def _test_reads_inner(self, buf, repeats=_REPEATS): slice = memoryview(buf)[offs : offs + _BLOCK_SZ] assert len(slice) == _BLOCK_SZ for r in range(repeats): - self.assertTrue( + self.assertIn( self.sd.readblocks(self.block, slice), + READBLOCKS_SUCCESS, "Read failed for block {} offs {} repeat {}/{}".format( self.block, offs, r, repeats ), @@ -148,7 +188,7 @@ def timer_cb(_): self.val = buf[self.scan % _TEST_BUF_SZ] self.scan += 1 - t = pyb.Timer(1, freq=30_000, callback=timer_cb, hard=True) + t = make_timer(timer_cb) self._test_reads_inner(buf) finally: if t: @@ -176,8 +216,7 @@ def timer_cb(t): if offs < _TEST_BUF_SZ - 3: buf[offs] = 0x56 - # pybd SF2 at default CPU frequency can manage >30kHz <40kHz - t = pyb.Timer(1, freq=35_000, callback=timer_cb, hard=True) + t = make_timer(timer_cb) self._test_reads_inner(buf) finally: if t: From de903e028065f9afd96af272811e04688b0422cf Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 4 Jun 2026 15:47:22 +1000 Subject: [PATCH 2/2] mimxrt: Workaround DCache invalidation problems with SDCard operations. - Invalidate the DCache after reads complete, to avoid stale cache lines that were read during the operation (the driver already cleans the cache before the operation). - Disable DMA if the read buffer isn't DCache aligned, to avoid corruption if the CPU writes to the same cache line as the buffer during the operation. The sdcard_dma_align.py test added in the parent commit is fixed by this commit. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/mimxrt/sdcard.c | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/ports/mimxrt/sdcard.c b/ports/mimxrt/sdcard.c index 84e2c98949d22..5428d328abef3 100644 --- a/ports/mimxrt/sdcard.c +++ b/ports/mimxrt/sdcard.c @@ -30,6 +30,7 @@ #include "sdcard.h" #include "ticks.h" +#include "fsl_cache.h" #include "fsl_iomuxc.h" #include "pin.h" @@ -294,18 +295,29 @@ static void sdcard_error_recovery(USDHC_Type *base) { static status_t sdcard_transfer_blocking(USDHC_Type *base, usdhc_handle_t *handle, usdhc_transfer_t *transfer, uint32_t timeout_ms) { status_t status; - usdhc_adma_config_t dma_config; - - (void)memset(&dma_config, 0, sizeof(usdhc_adma_config_t)); - dma_config.dmaMode = kUSDHC_DmaModeAdma2; - - #if !(defined(FSL_FEATURE_USDHC_HAS_NO_RW_BURST_LEN) && FSL_FEATURE_USDHC_HAS_NO_RW_BURST_LEN) - dma_config.burstLen = kUSDHC_EnBurstLenForINCR; + usdhc_adma_config_t dma_config = { + .dmaMode = kUSDHC_DmaModeAdma2, + #if !(defined(FSL_FEATURE_USDHC_HAS_NO_RW_BURST_LEN) && FSL_FEATURE_USDHC_HAS_NO_RW_BURST_LEN) + .burstLen = kUSDHC_EnBurstLenForINCR, + #endif + .admaTable = sdcard_adma_descriptor_table, + .admaTableWords = DMA_DESCRIPTOR_BUFFER_SIZE, + }; + usdhc_adma_config_t *p_dma_config = &dma_config; + + #if __DCACHE_PRESENT + size_t byte_len = transfer->data->blockCount * transfer->data->blockSize; + if ((uintptr_t)transfer->data->rxData % FSL_FEATURE_L1DCACHE_LINESIZE_BYTE != 0 || + byte_len % FSL_FEATURE_L1DCACHE_LINESIZE_BYTE != 0) { + // A DMA RX transfer that isn't cache line aligned can be corrupted if the CPU writes the same cache line during + // the read, so make a polling transfer instead. + // + // (Note that the USDHC driver will internally disable DMA if the address isn't 4 byte aligned, so this check only + // changes behaviour for addresses which are word aligned but not cache line aligned!) + p_dma_config = NULL; + } #endif - dma_config.admaTable = sdcard_adma_descriptor_table; - dma_config.admaTableWords = DMA_DESCRIPTOR_BUFFER_SIZE; - // Wait while the card is busy before a transfer status = kStatus_Timeout; for (int i = 0; i < timeout_ms * 100; i++) { @@ -313,7 +325,7 @@ static status_t sdcard_transfer_blocking(USDHC_Type *base, usdhc_handle_t *handl if (((transfer->data->txData == NULL) && (transfer->data->rxData == NULL)) || (USDHC_GetPresentStatusFlags(base) & (uint32_t)kUSDHC_Data0LineLevelFlag) != 0) { // Not busy anymore or no TX-Data - status = USDHC_TransferBlocking(base, &dma_config, transfer); + status = USDHC_TransferBlocking(base, p_dma_config, transfer); if (status != kStatus_Success) { sdcard_error_recovery(base); } @@ -321,6 +333,14 @@ static status_t sdcard_transfer_blocking(USDHC_Type *base, usdhc_handle_t *handl } ticks_delay_us64(10); } + + #if __DCACHE_PRESENT + if (p_dma_config && transfer->data->rxData) { + // Invalidate any cache lines that were filled while the transfer was in progress + L1CACHE_InvalidateDCacheByRange((uintptr_t)transfer->data->rxData, byte_len); + } + #endif + return status; }