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; } 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: