DMA 与缓存一致性

一、DMA 基础

二、DMA 控制器

三、DMA 地址与一致性

1. DMA 地址 vs 物理地址

2. DMA 方向

enum dma_data_direction {
    DMA_TO_DEVICE         = 1,    // 内存 → 设备
    DMA_FROM_DEVICE       = 2,    // 设备 → 内存
    DMA_BIDIRECTIONAL     = 3,
    DMA_NONE              = 0,
};

四、DMA 映射 API

1. 单缓冲区

// 一致性映射(coherent):任何时候 CPU 和设备看到的数据一致
//     - 性能差,但实现简单
//     - 适合命令缓冲区、控制结构
void *buf = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
// 使用:CPU 读写 buf;告诉设备 dma_handle
dma_free_coherent(dev, size, buf, dma_handle);

// 流式映射(streaming):临时使用
//     - 性能好,但需要手动 sync
dma_addr_t dma = dma_map_single(dev, buf, size, DMA_TO_DEVICE);
if (dma_mapping_error(dev, dma)) { /* 错误处理 */ }
// 设备使用 dma
dma_unmap_single(dev, dma, size, DMA_TO_DEVICE);

// 流式 sync(CPU 侧)
dma_sync_single_for_cpu(dev, dma, size, DMA_FROM_DEVICE);
dma_sync_single_for_device(dev, dma, size, DMA_TO_DEVICE);

2. 分散/聚集(scatter-gather)

struct scatterlist sg[4];
sg_init_table(sg, 4);
// 填充 sg
int n = dma_map_sg(dev, sg, 4, DMA_TO_DEVICE);
for (i = 0; i < n; i++) {
    // 设备使用 sg_dma_address(sg+i)、sg_dma_len(sg+i)
}
dma_unmap_sg(dev, sg, n, DMA_TO_DEVICE);

3. 大块 DMA(CMA)

// 用 CMA 分配大块连续物理内存
struct page *p = dma_alloc_from_dev_coherent(dev, size, &dma, GFP_KERNEL);

五、缓存一致性问题

CPU 有缓存,DMA 直接读物理内存:
- CPU 写到缓存但未刷新到内存 → 设备看不到
- 设备写入内存但 CPU 缓存未失效 → CPU 看到旧值

解决:
- 一致性映射:dma_alloc_coherent 已经禁用缓存
- 流式:dma_sync_* 同步

六、Cache 操作 API

void flush_dcache_page(struct page *page);
void invalidate_dcache_page(struct page *page);
void flush_kernel_dcache_page(void *addr);
void clean_dcache_area(void *addr, size_t len);

ARM 64 位常用:

void __dma_map_area(const void *, size_t, int);   // 内部使用
void __dma_unmap_area(const void *, size_t, int);
void __dma_flush_area(const void *, size_t);
void __dma_inv_area(const void *, size_t);

七、DMA 池

struct dma_pool *pool = dma_pool_create("mypool", dev, 32, 32, 0);
void *buf = dma_pool_alloc(pool, GFP_KERNEL, &dma);
dma_pool_free(pool, buf, dma);
dma_pool_destroy(pool);

适合大量小对象(如描述符)的分配。

八、SKB 辅助

// 网络驱动
dma_addr_t dma = dma_map_single(dev, skb->data, skb->len, DMA_TO_DEVICE);

九、设备树

dma-controller@40000000 {
    compatible = "arm,pl330";
    reg = <0x40000000 0x1000>;
    #dma-cells = <1>;
    interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
};

mydevice {
    dmas = <&dma0 0 1 0x12345678>;    // 控制器、请求线、通道、配置
    dma-names = "tx", "rx";
};

十、嵌入式 DMA 注意