1. Тема дня

DMA позволяет периферии читать и писать RAM без участия CPU. Это удобно для ADC, UART, SPI, CAN, Ethernet и больших потоков данных, но появляется новая проблема: кто сейчас владеет буфером и видят ли CPU и DMA одинаковую версию данных. Типовой путь:

text
ADC / UART / SPI / CAN / Ethernet
        -> DMA
        -> DMA buffer pool
        -> ownership transfer
        -> FreeRTOS owner-task
        -> parser / DSP / FSM

Главная мысль: zero-copy начинается не с отсутствия mem- cpy(), а с явного владения буфером. Если DMA и CPU одновременно считают буфер своим, zero-copy превращается в race condition.

2. Зачем это нужно в твоих проектах

Для ADC-детектирования фаз лучше использовать ping-pong/ring DMA: пока DMA пишет блок B, CPU обрабатывает блок A. Для EC25 и больших MQTT payload полезно передавать pointer + length + ownership token, а не копировать 4 КБ несколько раз. Для CAN/RS-485 gateway выгодно передавать frame descriptors между задачами вместо массивов байтов. На ESP32-P4 и STM32H7/F7 тема особенно важна из-за cache coherency. CPU может держать свежие данные в cache, а DMA видеть старую RAM, или наоборот.

3. Теория

Zero-copy как ownership protocol

Обычный путь:

text
DMA buffer -> memcpy -> parser buffer -> memcpy -> message object -> memcpy -> application

Zero-copy путь:

text
DMA buffer -> pointer + length + timestamp + ownership token -> application

Пример descriptor:

c
typedef struct {
    uint8_t *data;
    size_t length;
    uint32_t buffer_id;
    uint32_t generation;
    uint64_t timestamp_us;
} buffer_view_t;

Состояния RX-буфера

text
FREE -> DMA_OWNED -> CPU_READY -> CPU_OWNED -> FREE

Недопустимо, чтобы DMA писал буфер, пока CPU его обрабатывает. TX-цикл

text
FREE -> CPU_FILLING -> READY_FOR_DMA -> DMA_OWNED -> FREE

После запуска DMA CPU не должен изменять TX-буфер до comple- tion event.

Ping-pong DMA

text
Time 0: DMA writes A
Time 1: DMA writes B, CPU processes A
Time 2: DMA writes A, CPU processes B

Если обработка CPU иногда дольше периода DMA, двух буферов мало — нужен ring/pool и backpressure policy.

Cache coherency

CPU -> DMA:

text
CPU изменил buffer -> clean cache -> RAM обновлена -> DMA читает правильные данные

DMA -> CPU:

text
DMA записал RAM -> invalidate cache -> CPU читает свежие данные из RAM

На STM32 Cortex-M7 используются SCB_CleanDCache_by_Addr() и SCB_InvalidateDCache_by_Addr(). На ESP32-P4 используется esp_cache_msync() с направлениями C2M и M2C.

Почему важна cache-line alignment

Если DMA-буфер делит cache line с обычной переменной, in- validate может выбросить dirty данные соседней переменной. Поэтому DMA-буфер должен занимать собственные cache lines, а размер и адрес должны быть выровнены.

volatile не решает coherency

volatile не делает cache clean, не делает invalidate, не является memory barrier и не делает доступ thread-safe.

4. Типичные ошибки

  1. Отдать DMA обычный malloc()-буфер без проверки memory capability.
  2. Использовать stack buffer для асинхронного DMA.
  3. Менять TX-буфер после запуска DMA.
  4. Читать RX-буфер до completion event.
  5. На M7 забыть invalidate D-cache после RX DMA.
  6. Перед TX сделать invalidate вместо clean.
  7. Считать volatile заменой cache maintenance.
  8. Инвалидировать невыравненную cache region и повредить соседние данные.
  9. Забыть синхронизировать DMA descriptors.
  10. Передавать 4-КБ структуры через FreeRTOS queue.
  11. Передавать pointer без lifetime protocol.
  12. Не иметь generation в reusable buffer handle.
  13. Ждать свободный буфер в ISR.
  14. Давать logger удерживать realtime buffer.
  15. Пытаться сделать zero-copy для маленьких control messages, где копирование проще и безопаснее.

5. Практическое задание на 30-60 минут

Создай DMA_BUFFER_POLICY.md:

markdown
# DMA buffer policy
1. Every DMA buffer has exactly one owner.
2. Ownership transitions are explicit.
3. DMA buffers are never allocated in ISR.
4. Buffers are aligned to DMA/cache requirements.
5. CPU does not access DMA-owned buffers.
6. Cache synchronization occurs only at ownership boundaries.
7. FreeRTOS queues pass handles, not large payloads.
8. Every reusable buffer has a generation number.
9. Pool exhaustion has an explicit drop/degraded policy.
10. Driver-owned buffers are never retained beyond documented lifetime.

Затем реализуй маленький pool:

c
#define DMA_POOL_COUNT 4U
#define DMA_BLOCK_SIZE 1024U
#define DMA_ALIGNMENT 32U
typedef enum {
    BUFFER_FREE = 0,
    BUFFER_DMA_OWNED,
    BUFFER_CPU_READY,
    BUFFER_CPU_OWNED,
} buffer_state_t;
typedef struct {
    _Alignas(DMA_ALIGNMENT) uint8_t data[DMA_BLOCK_SIZE];
    uint16_t generation;
    uint16_t state;
    uint32_t valid_length;
    uint32_t sequence;
} dma_buffer_t;
typedef struct {
    dma_buffer_t buffers[DMA_POOL_COUNT];
    uint32_t allocation_failures;
    uint32_t stale_handles;
    uint32_t invalid_transitions;
} dma_buffer_pool_t;

Проверь transitions:

text
FREE -> DMA_OWNED -> CPU_READY -> CPU_OWNED -> FREE

И ошибки:

text
CPU acquire before DMA completion
second completion of same handle
double release
stale handle after buffer reuse
received > capacity
pool exhausted
generation mismatch

6. Что попробовать дальше

  • Для ESP32-P4 изучить esp_cache_msync(), MALLOC_CAP_DMA, MALLOC_CAP_CACHE_ALIGNED, MALLOC_CAP_DMA_DESC_AHB/AXI.
  • Для STM32H7/F7 изучить AN4839 и CMSIS D-cache API.
  • Измерить CPU time и latency для memcpy-pipeline и handle- based zero-copy pipeline.
CPU заполнил TX buffer, затем его будет читать DMA. Какая cache operation соответствует этому направлению в уроке?

Задание

Проследите RX handle через FREE -> DMA_OWNED -> CPU_READY -> CPU_OWNED -> FREE. Когда CPU может читать данные и почему старый handle нужно отклонить после повторного использования buffer?

Критерии самопроверки: Укажите completion, coherency, исключительное владение CPU и проверку generation.

Показать ответ автора

CPU читает после DMA completion и необходимой cache maintenance, когда handle получен в CPU_OWNED. Release возвращает buffer в FREE. Несовпадение generation обозначает старый handle уже переиспользованного buffer, которому нельзя читать или освобождать данные нового владельца.