Linux DMA开发指南:原理、API与实战优化
1. Linux DMA开发概述DMADirect Memory Access是现代计算机系统中至关重要的数据传输机制它允许外设设备直接与内存交换数据而无需CPU介入。在Linux内核开发中DMA技术被广泛应用于网络设备、存储控制器、音视频采集卡等高性能外设的驱动开发。注意DMA操作涉及硬件直接访问内存错误的配置可能导致系统崩溃或数据损坏开发时需要特别谨慎。1.1 DMA工作原理DMA控制器作为独立的硬件单元通过以下流程完成数据传输CPU初始化DMA控制器设置源地址、目标地址和传输长度DMA控制器向总线仲裁器请求总线控制权获得授权后DMA控制器开始数据传输传输完成后DMA控制器通过中断通知CPU在x86架构中DMA控制器通过PCIe总线与设备通信而在ARM架构中通常使用AXI总线协议实现DMA功能。2. Linux DMA开发环境准备2.1 内核配置要求开发DMA驱动需要确保内核已启用以下配置选项CONFIG_DMA_ENGINEy CONFIG_DMA_VIRTUAL_CHANNELSy CONFIG_DMA_ACPIy CONFIG_DMA_OFy对于特定架构还需要启用对应的DMA引擎驱动例如CONFIG_XILINX_DMAy # Xilinx FPGA DMA支持 CONFIG_IMX_SDMAy # i.MX系列DMA控制器2.2 开发工具链推荐使用以下工具进行DMA驱动开发GCC交叉编译工具链与目标平台匹配perf工具用于性能分析ioctl测试程序用于验证驱动接口逻辑分析仪用于硬件信号调试3. Linux DMA API详解3.1 主要数据结构Linux内核提供了完整的DMA API抽象层核心数据结构包括struct dma_device { struct device *dev; const struct dma_device_ops *device_ops; // ...其他成员 }; struct dma_chan { struct dma_device *device; dma_cookie_t cookie; // ...其他成员 }; struct dma_async_tx_descriptor { struct dma_chan *chan; dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx); // ...其他成员 };3.2 常用API函数分配DMA通道struct dma_chan *dma_request_chan(struct device *dev, const char *name);配置DMA传输参数struct dma_slave_config { enum dma_transfer_direction direction; dma_addr_t src_addr; dma_addr_t dst_addr; enum dma_slave_buswidth src_addr_width; enum dma_slave_buswidth dst_addr_width; u32 src_maxburst; u32 dst_maxburst; // ...其他成员 }; int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config);准备DMA描述符struct dma_async_tx_descriptor *dmaengine_prep_slave_sg( struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len, enum dma_transfer_direction direction, unsigned long flags);提交DMA传输dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc);启动DMA传输void dma_async_issue_pending(struct dma_chan *chan);4. DMA驱动开发实战4.1 编写DMA驱动模块以下是一个简单的DMA驱动框架示例#include linux/module.h #include linux/dmaengine.h #include linux/dma-mapping.h struct my_dma_device { struct dma_device dma_dev; struct dma_chan *chan; // 设备特定数据 }; static int my_dma_probe(struct platform_device *pdev) { struct my_dma_device *mdev; struct dma_device *dma_dev; int ret; mdev devm_kzalloc(pdev-dev, sizeof(*mdev), GFP_KERNEL); if (!mdev) return -ENOMEM; dma_dev mdev-dma_dev; dma_dev-dev pdev-dev; // 初始化DMA设备操作集 dma_dev-device_alloc_chan_resources my_alloc_chan; dma_dev-device_free_chan_resources my_free_chan; dma_dev-device_prep_slave_sg my_prep_slave_sg; dma_dev-device_issue_pending my_issue_pending; dma_dev-device_tx_status my_tx_status; // 注册DMA设备 ret dma_async_device_register(dma_dev); if (ret) { dev_err(pdev-dev, Failed to register DMA device\n); return ret; } platform_set_drvdata(pdev, mdev); return 0; } // 其他必要函数实现...4.2 DMA内存分配DMA操作需要特殊的内存分配方式Linux提供了多种API一致性DMA映射Coherent DMAvoid *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag);流式DMA映射Streaming DMAdma_addr_t dma_map_single(struct device *dev, void *ptr, size_t size, enum dma_data_direction dir);重要提示流式DMA映射必须与dma_unmap_single配对使用且不能在映射期间访问缓冲区。5. 性能优化与调试技巧5.1 DMA性能优化分散/聚集(SG)列表对于非连续内存区域使用scatter-gather列表可以减少内存拷贝struct scatterlist *sg; sg_init_table(sg, nents); for (i 0; i nents; i) { sg_set_page(sg[i], pages[i], PAGE_SIZE, 0); }描述符链预先准备多个传输描述符并链接起来减少中断开销struct dma_async_tx_descriptor *tx1, *tx2; tx1 dmaengine_prep_...(); tx2 dmaengine_prep_...(); tx1-next tx2;环形缓冲区实现生产者-消费者模型提高吞吐量struct ring_buffer { void *virt_addr; dma_addr_t dma_addr; size_t size; u32 head, tail; };5.2 常见问题排查DMA传输失败检查DMA通道是否成功分配dma_request_chan返回值验证源/目标地址是否在DMA区域dma_addr_valid确认DMA方向配置正确DMA_TO_DEVICE/DMA_FROM_DEVICE数据不一致确保正确使用dma_sync_single_for_{cpu,device}检查CPU缓存是否与DMA视图一致验证是否启用了正确的内存屏障mb(), rmb(), wmb()性能瓶颈使用perf工具分析DMA中断频率检查DMA描述符处理延迟评估是否启用DMA引擎的硬件加速特性6. 高级DMA特性6.1 分散聚集DMA现代DMA控制器通常支持scatter-gather功能允许单个DMA传输操作多个不连续的内存区域struct scatterlist sg[3]; void *buf1, *buf2, *buf3; // 初始化缓冲区 buf1 kmalloc(size1, GFP_KERNEL); buf2 kmalloc(size2, GFP_KERNEL); buf3 kmalloc(size3, GFP_KERNEL); // 设置scatterlist sg_init_table(sg, 3); sg_set_buf(sg[0], buf1, size1); sg_set_buf(sg[1], buf2, size2); sg_set_buf(sg[2], buf3, size3); // 准备SG DMA传输 txd dmaengine_prep_slave_sg(chan, sg, 3, DMA_MEM_TO_DEV, flags);6.2 DMA池技术对于频繁的小块DMA内存分配可以使用DMA池提高效率struct dma_pool *pool; // 创建DMA池 pool dma_pool_create(my_pool, dev, size, align, boundary); // 从池中分配内存 void *buf dma_pool_alloc(pool, GFP_KERNEL, dma_addr); // 释放内存回池 dma_pool_free(pool, buf, dma_addr); // 销毁池 dma_pool_destroy(pool);6.3 中断合并高吞吐量场景下可以通过中断合并减少CPU开销// 在DMA引擎驱动中设置中断合并参数 struct dma_slave_caps caps; dma_get_slave_caps(chan, caps); if (caps.descriptor_reuse) { // 支持描述符重用可以合并中断 txd-flags | DMA_PREP_INTERRUPT; txd-callback_param my_data; }7. 实际案例UART DMA驱动实现以下是通过DMA实现UART高效数据传输的典型流程初始化阶段// 请求DMA通道 tx_chan dma_request_chan(dev, tx); rx_chan dma_request_chan(dev, rx); // 配置DMA参数 struct dma_slave_config config { .direction DMA_MEM_TO_DEV, .dst_addr uart_phys_addr UART_DR, .dst_addr_width DMA_SLAVE_BUSWIDTH_1_BYTE, }; dmaengine_slave_config(tx_chan, config);发送数据// 准备DMA描述符 tx_desc dmaengine_prep_slave_single(tx_chan, dma_buf, len, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT); // 设置完成回调 tx_desc-callback uart_dma_tx_complete; tx_desc-callback_param uart; // 提交并启动传输 dmaengine_submit(tx_desc); dma_async_issue_pending(tx_chan);接收数据// 准备循环缓冲区 rx_desc dmaengine_prep_dma_cyclic(rx_chan, dma_buf, BUF_SIZE, BUF_SIZE/2, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); rx_desc-callback uart_dma_rx_complete; dmaengine_submit(rx_desc); dma_async_issue_pending(rx_chan);中断处理static void uart_dma_rx_complete(void *param) { struct uart_port *port param; unsigned int received; // 计算接收到的数据量 received dma_get_residue(rx_chan); received BUF_SIZE - received; // 处理数据 process_rx_data(port, rx_buf, received); // 重新提交DMA请求 dmaengine_terminate_all(rx_chan); dmaengine_submit(rx_desc); dma_async_issue_pending(rx_chan); }经验分享在UART DMA实现中合理设置FIFO触发级别可以显著提高小数据包传输效率。通常建议将RX FIFO触发级别设置为1/4 FIFO深度TX FIFO触发级别设置为1/2 FIFO深度。