Angus Gratton 7fd8a6d4bc stm32/dma: Add D-cache protection for DMA RX operations, including SPI.
This new DMA API corrects possible cache coherency issues on chips with
D-Cache, when working with buffers at arbitrary memory locations (i.e.
supplied by Python code).

The API is used by SPI to fix an issue with corrupt data when reading from
SPI using DMA in certain cases.  A regression test is included (it depends
on external hardware connection).

Explanation:

1) It's necessary to invalidate D-Cache after a DMA RX operation completes
   in case the CPU reads (or speculatively reads) from the DMA RX region
   during the operation.  This seems to have been the root cause of issue
   #13471 (only when src==dest for this case).

2) More generally, it is also necessary to temporarily mark the first and
   last cache lines of a DMA RX operation as "uncached", in case the DMA
   buffer shares this cache line with unrelated data.  The CPU could
   otherwise write the other data at any time during the DMA operation (for
   example from an interrupt handler), creating a dirty cache line that's
   inconsistent with the DMA result.

Fixes issue #13471.

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <angus@redyak.com.au>
2024-03-08 12:19:48 +11:00

44 lines
1.4 KiB
Python

from machine import SPI
# Regression test for DMA for DCache coherency bugs with cache line
# written originally for https://github.com/micropython/micropython/issues/13471
# IMPORTANT: This test requires SPI2 MISO (pin Y8 on Pyboard D) to be connected to GND
SPI_NUM = 2
spi = SPI(SPI_NUM, baudrate=5_000_000)
buf = bytearray(1024)
ok = True
for offs in range(0, len(buf)):
v = memoryview(buf)[offs : offs + 128]
spi.readinto(v, 0xFF)
if not all(b == 0x00 for b in v):
print(offs, v.hex())
ok = False
print("Variable offset fixed length " + ("OK" if ok else "FAIL"))
# this takes around 30s to run, so skipped if already failing
if ok:
for op_len in range(1, 66):
wr = b"\xFF" * op_len
for offs in range(1, len(buf) - op_len - 1):
# Place some "sentinel" values before and after the DMA buffer
before = offs & 0xFF
after = (~offs) & 0xFF
buf[offs - 1] = before
buf[offs + op_len] = after
v = memoryview(buf)[offs : offs + op_len]
spi.write_readinto(wr, v)
if (
not all(b == 0x00 for b in v)
or buf[offs - 1] != before
or buf[offs + op_len] != after
):
print(v.hex())
print(hex(op_len), hex(offs), hex(buf[offs - 1]), hex(buf[offs + op_len]))
ok = False
print("Variable offset and lengths " + ("OK" if ok else "FAIL"))