DMA 与缓存一致性
一、DMA 基础
- DMA(Direct Memory Access):外设直接访问内存,CPU 不参与
- 比 CPU 拷贝更快、解放 CPU
- 嵌入式:网卡、UART、I2S、SPI、SD 都用 DMA
二、DMA 控制器
- 系统 DMA:SoC 集成(如 PL330)
- 外设自带的 DMA:网卡、SPI 控制器自带的 FIFO + DMA
三、DMA 地址与一致性
1. DMA 地址 vs 物理地址
- 在 IOMMU(ARM SMMU)启用时,DMA 地址 ≠ 物理地址
- 驱动必须用 DMA API
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 注意
- 对齐:DMA 缓冲区通常需要 cache line 对齐(32 或 64 字节)
- 大小:dma_addr_t 是 32/64 位,注意跨平台
- 错误处理:必须检查
dma_mapping_error - 同步:CPU 与设备的同步要正确
- 内存屏障:DMA 操作后可能需要 smp_wmb()
- 多核一致性:CPU 之间也要同步