DMA原理、Cache一致性问题与配置一、RK3588平台DMA传输基本原理1.1 RK3588硬件架构基础要理解RK3588的DMA与Cache一致性首先需要明确其总线与缓存层级CPU缓存层级RK3588采用ARM DynamIQ架构4核Cortex-A76 4核Cortex-A55共享3MB L3缓存由DSU单元管理其中每个A76核私有64KB L1指令缓存、64KB L1数据缓存、512KB L2缓存每个A55核私有32KB L1指令/数据缓存、128KB L2缓存。CPU默认通过Cache以写回Write Back策略访问主存以提升访问性能。总线互连与一致性域CPU簇通过ACEAXI Coherency Extensions总线连接到高速互连矩阵仅GPUMali-G610、NPU、VPU等高速多媒体外设接入了硬件一致性域可自动监听CPU Cache而通用DMA控制器、SPI/UART/GMAC等低速外设挂载在普通AXI总线上不属于一致性域无法感知CPU Cache内容。DMA控制器RK3588集成2路基于ARM PL330衍生的DMACv2控制器每路支持8个独立通道主要服务于UART、SPI、I2C、音频等外设的大数据量传输属于典型的非一致性DMA主设备直接访问物理内存不经过CPU Cache通路。1.2 RK3588上DMA传输完整流程DMA的核心价值是解放CPU让外设与内存直接交换数据在RK3588上的完整执行步骤如下CPU配置阶段驱动程序通过DMA Engine接口向DMAC控制器写入传输参数源地址外设寄存器/内存、目的地址内存/外设寄存器、传输长度、数据宽度、突发长度、传输方向并绑定完成中断回调函数。通道启动与总线接管CPU启动DMA通道后DMAC独立监听外设的DMA请求信号当外设准备就绪时DMAC通过总线仲裁获得AXI总线控制权直接在内存与外设FIFO之间搬运数据CPU可并行执行其他计算任务。数据传输阶段DMAC按照配置的突发长度逐次传输数据内部维护地址自增与剩余计数全程无需CPU介入多通道之间可通过优先级调度实现分时复用。完成通知阶段传输计数归零时DMAC向GIC中断控制器发送中断信号CPU响应中断后执行回调函数处理传输完成的业务逻辑如校验数据、释放缓冲区。二、RK3588上Cache一致性问题的产生原因2.1 核心矛盾CPU与DMA访问主存的通路完全独立CPU访问内存CPU → L1 Cache → L2 Cache → L3 Cache → 主存DDRDMA访问内存DMAC → AXI总线 → 主存DDR不经过任何CPU Cache层级由于CPU采用写回缓存策略数据修改后可能只存在于Cache中尚未同步到主存而DMA写入主存的新数据也无法主动更新CPU Cache最终导致双方看到的数据不一致。2.2 两种典型不一致场景RK3588实例场景1DMA_TO_DEVICE内存 → 外设DMA读内存流程CPU先修改内存缓冲区数据 → 启动DMA将缓冲区数据发送到外设如SPI发送、UART发送问题CPU修改的数据仅写入L1/L2 Cache未触发回写到DDRDMA直接从DDR读取数据得到的是过期的旧数据导致外设发送错误内容。场景2DMA_FROM_DEVICE外设 → 内存DMA写内存流程启动DMA接收外设数据写入内存 → CPU读取该内存缓冲区处理数据如SPI接收、网卡收包问题DMA将新数据写入DDR但CPU Cache中仍保留该地址的旧数据CPU读取时直接命中Cache得到脏数据无法获取外设传输的最新内容。2.3 RK3588的特殊性RK3588的通用PL330 DMAC未接入硬件一致性域因此所有通用外设的DMA传输默认都存在Cache一致性风险仅GPU、NPU等接入ACE总线的模块可通过硬件自动监听SnoopCPU Cache无需软件额外处理。官方SDK默认在SoC级设备树中标记了dma-noncoherent属性明确告知内核通用外设不支持硬件一致性需由软件维护Cache同步。三、RK3588平台常见解决方案与配置步骤方案1硬件一致性仅支持特定外设原理接入ACE一致性域的主设备如GPU、NPU发起DMA传输时总线互连单元会自动监听CPU CacheDMA读内存时若CPU Cache中有脏数据自动先回写到DDR再让DMA读取DMA写内存时自动将CPU Cache中对应地址的数据标记为无效CPU下次读取时直接从DDR加载RK3588配置方式在对应外设的设备树节点中添加dma-coherent属性告知内核该设备支持硬件一致性无需软件维护Cachegpu { dma-coherent; // 标记GPU DMA为硬件一致 status okay; };注意通用外设SPI/UART/DMAC不能添加该属性否则会导致数据不一致且无法通过软件修复。方案2一致性DMA映射dma_alloc_coherent原理从内存中分配一块非缓存Non-cacheable内存区域CPU和DMA都直接访问DDR不经过Cache从根源上消除数据不同步的问题。在RK3588上该接口默认分配Normal Non-Cacheable属性的内存CPU读写无缓存加速但数据永远一致。适用场景长期存在、频繁进行DMA传输的缓冲区如网卡环形缓冲区、音频DMA缓冲区。RK3588配置步骤步骤1内核配置开启依赖# 内核menuconfig配置Device Drivers ---DMA Engine support ---[*]SupportforARM PL330 DMA controller# 开启PL330驱动[*]DMA Contiguous Memory Allocator# 开启CMA用于连续物理内存分配General setup ---[*]Contiguous Memory Allocation步骤2设备树预留CMA内存可选大缓冲区建议配置在设备树reserved-memory节点中预留连续内存池供DMA分配使用reserved-memory { #address-cells 2; #size-cells 2; ranges; dma_pool: dma-pool35000000 { compatible shared-dma-pool; reusable; reg 0x0 0x35000000 0x0 0x08000000; // 预留128MB起始地址0x35000000 linux,cma-default; }; };步骤3驱动代码中分配与使用/* 分配一致性DMA缓冲区 */dma_addr_tdma_handle;// DMA总线地址void*virt_addr;// CPU虚拟地址// 分配4KB大小的一致性内存GFP_KERNEL表示内核上下文分配virt_addrdma_alloc_coherent(dev,4096,dma_handle,GFP_KERNEL);if(!virt_addr){return-ENOMEM;}/* CPU直接读写缓冲区无需Cache操作 */memcpy(virt_addr,tx_data,len);/* 启动DMA传输传入dma_handle作为DMA地址 */dmaengine_submit(tx_desc);dma_async_issue_pending(chan);/* 释放缓冲区 */dma_free_coherent(dev,4096,virt_addr,dma_handle);RK3588注意事项部分外设DMA仅支持32位物理地址分配时需添加GFP_DMA32标志确保分配的内存在4GB地址空间内。方案3流式DMA映射dma_map_single原理缓冲区本身使用普通缓存内存仅在DMA传输前后执行Cache同步操作DMA传输前map阶段根据方向同步CacheDMA_TO_DEVICE执行Cache Clean刷写将Cache中脏数据回写到DDRDMA_FROM_DEVICE执行Cache Invalidate失效丢弃Cache中旧数据DMA传输完成后unmap阶段反向同步确保CPU能读到最新数据该方案兼顾了CPU缓存性能与DMA一致性是Linux驱动中最常用的方案。适用场景单次、短时的DMA传输如SPI单次读写、文件块传输。RK3588配置与代码示例#includelinux/dma-mapping.h/* 1. 定义缓冲区与DMA地址 */u8*bufkmalloc(4096,GFP_KERNEL);// 普通缓存内存dma_addr_tdma_addr;/* 2. DMA传输前映射并同步Cache */// DMA_TO_DEVICE方向内存→外设刷写Cache到DDRdma_addrdma_map_single(dev,buf,4096,DMA_TO_DEVICE);if(dma_mapping_error(dev,dma_addr)){kfree(buf);return-EIO;}/* 3. 启动DMA传输 */tx_descdmaengine_prep_slave_single(tx_chan,dma_addr,4096,DMA_MEM_TO_DEV,DMA_PREP_INTERRUPT);dmaengine_submit(tx_desc);dma_async_issue_pending(tx_chan);/* 4. 等待DMA传输完成中断/轮询 */wait_for_completion(tx_done);/* 5. 传输完成解除映射 */dma_unmap_single(dev,dma_addr,4096,DMA_TO_DEVICE);/* 6. CPU可正常访问缓冲区 */kfree(buf);接收方向DMA_FROM_DEVICE逻辑一致仅方向参数替换为DMA_FROM_DEVICEmap时会失效Cacheunmap后CPU读取的就是DMA写入的最新数据。方案4手动Cache维护底层兜底原理直接调用ARM64架构层的Cache操作接口手动对指定内存区域执行刷写、失效操作属于最底层的兜底方案一般不推荐直接使用仅在特殊自定义场景下使用。RK3588可用接口#includeasm/cacheflush.h// 刷写指定范围Cache到内存对应DMA_TO_DEVICE场景flush_cache_virt((unsignedlong)virt_addr,(unsignedlong)virt_addrsize);// 失效指定范围Cache对应DMA_FROM_DEVICE场景invalidate_cache_virt((unsignedlong)virt_addr,(unsignedlong)virt_addrsize);// 刷写并失效读写混合场景flush_dcache_range((unsignedlong)virt_addr,(unsignedlong)virt_addrsize);注意手动操作必须严格按缓存行对齐RK3588缓存行大小为64字节否则会导致相邻数据被意外修改。四、RK3588设备树DMA完整配置示例4.1 DMA控制器节点SoC dtsi默认已定义dmac0: dma-controllerfea10000 { compatible arm,pl330, arm,primecell; reg 0x0 0xfea10000 0x0 0x4000; interrupts GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH, GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH; #dma-cells 1; // 1个参数表示通道请求号 arm,pl330-periph-burst; // 支持外设突发模式 clocks cru ACLK_DMAC0; clock-names apb_pclk; dma-noncoherent; // 标记为非一致性设备 };4.2 外设绑定DMA通道以SPI0为例spi0 { status okay; pinctrl-names default; pinctrl-0 spi0m0_cs0 spi0m0_pins; // 绑定DMA通道tx对应dmac0的14号请求rx对应15号请求 dmas dmac0 14, dmac0 15; dma-names tx, rx; };五、RK3588开发注意事项地址对齐要求DMA缓冲区建议按64字节缓存行大小对齐避免Cache操作时影响相邻数据大流量场景建议按4KB页对齐。32位地址限制部分外设DMA控制器仅支持32位寻址分配内存时需使用GFP_DMA32标志避免分配到4GB以上地址导致传输错误。CMA大小配置大尺寸DMA缓冲区如视频、ISP需提前预留足够CMA空间默认SDK通常配置256MB CMA可通过内核启动参数cma512M调整。禁止跨操作访问DMA映射期间CPU禁止访问缓冲区否则可能破坏数据一致性必须在unmap之后再由CPU读写。