DMA驱动框架流程编写

本文深入探讨Linux设备驱动中的DMA驱动技术,讲解DMA驱动的基本原理及一致性DMA的处理流程,通过实例阐述如何编写DMA驱动框架。

本文主要是针对Xilinx DMA驱动流程框架编写
DMA驱动一致性和流式的基本认识
一致性DMA与流式DMA主要是内存的申请方式和访问控制权限不一样
一致性DMA内存申请:
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);


流式DMA内存申请:
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction); 
 如果映射成功,返回的是总线地址,否则返回NULL.最后一个参数DMA的方向,可能取DMA_TO_DEVICE, DMA_FORM_DEVICE, DMA_BIDIRECTIONAL和DMA_NONE


一致性申请的内存区域DMA和CPU能够同时访问,所以可以认为一致性是同步的
流式申请的内存区域DMA和CPU不能同时访问,DMA释放之后CPU才能访问,所以认为是异步的


初始化DMA
(1)dma_cap_zero(mask);
宏定义如下
#define dma_cap_zero(mask) __dma_cap_zero(&(mask))
static inline void __dma_cap_zero(dma_cap_mask_t *dstp)
{
bitmap_zero(dstp->bits, DMA_TX_TYPE_END);
}
将mask清零;
(2)dma_cap_set(DMA_SLAVE | DMA_PRIVATE, mask);
设置mask,DMA传输通道类型
   direction = DMA_DEV_TO_MEM;//DMA传输方向
match = (direction & 0xFF) | XILINX_DMA_IP_DMA;//DMA匹配类型(外设ID)
(3)DMA通道申请
dma_request_channel(mask, axi_adc_filter, &match);
static bool axi_adc_filter(struct dma_chan *chan, void *param)


一致性内存申请
dma_alloc_coherent(rx_dev->dev,AXI_XADC_BUFF_SIZE,&(axi_xadc_dev->dma_dsts),GFP_ATOMIC); 
流式内存映射
axi_xadc_dev->dma_dsts = dma_map_single(rx_dev->dev,axi_xadc_dev->dsts,AXI_XADC_BUFF_SIZE,
DMA_DEV_TO_MEM); 
     一致性内存:设备和CPU能同时访问这块内存,无需担心cache的影响;
流式:DMA必须释放内存之后,CPU才能访问这块内存
设备申请AXI_XADC_BUFF_SIZE字节大小的一致性内存;函数返回两个参数,一个是申请的虚拟内存的首地址;另一个是内存的物理地址保存在axi_xadc_dev->dma_dsts给DMA操作;GFP_ATOMIC为申请的内存flags。
通道的配置
config.coalesc = 1;中断级联门限
config.delay = 0;延时
rx_dev->device_control(axi_xadc_dev->rx_chan, DMA_SLAVE_CONFIG, (unsigned long) &config);
驱动接口函数为:
xilinx_dma_device_control(struct dma_chan *dchan,enum dma_ctrl_cmd cmd, 
unsigned long arg)
     DMA_SLAVE_CONFIG:DMA从配置命令字
     Config:配置参数
获取BD描述符
dmaengine_prep_slave_single(struct dma_chan *chan, dma_addr_t buf, size_t len,
                    enum dma_transfer_direction dir, unsigned long flags)
{
struct scatterlist sg;
sg_init_table(&sg, 1);初始化sg数组
sg_dma_address(&sg) = buf;dma地址
sg_dma_len(&sg) = len;dma数据长度


return chan->device->device_prep_slave_sg(chan, &sg, 1,
 dir, flags, NULL);
}
本文直接调用驱动接口函数
sg_init_table(&axi_xadc_dev->rx_sg, AXI_XADC_BUF_COUNT);
sg_dma_address(&axi_xadc_dev->rx_sg) = axi_xadc_dev->dma_dsts;
sg_dma_len(&axi_xadc_dev->rx_sg) = AXI_XADC_BUFF_SIZE;
device_prep_slave_sg(axi_xadc_dev->rx_chan,
&axi_xadc_dev->rx_sg,
AXI_XADC_BUF_COUNT,
DMA_DEV_TO_MEM,
axi_xadc_dev->flags, NULL);


钩子挂接函数接口为:
static struct dma_async_tx_descriptor * xilinx_dma_prep_slave_sg(
struct dma_chan *dchan, struct scatterlist *sgl, unsigned int sg_len,
enum dma_transfer_direction direction, unsigned long flags, void *context)
参数说明:struct dma_chan *dchan:DMA通道
 struct scatterlist *sgl:sg数组首地址,头指针
 unsigned int sg_len:sg数组表项个数
 enum dma_transfer_direction direction:枚举类型,传输方向
 unsigned long flags:传输响应标志
{
struct xilinx_dma_desc_hw *hw;
struct xilinx_dma_transfer *t;
struct xilinx_dma_chan *chan;
<think>嗯,用户想了解DMA代码编写的步骤和流程。从用户的问题来看,应该是个嵌入式开发者,正在学习DMA编程。用户可能刚接触DMA,需要从基础开始理解实现流程。 用户提供的引用资料很有价值,特别是引用[2]详细描述了AXI DMA的使用流程。结合这些资料和我掌握的知识,DMA编程可以归纳为三个核心步骤:初始化、配置和启动传输。这个框架适用于大多数平台,包括STM32、LPC1700等。 用户可能更关心具体实现细节,比如如何设置源地址/目的地址、如何处理缓存一致性等实际问题。引用[4]提到的外设协作思想也很重要,DMA的价值就在于解放CPU,这个概念需要强调。 考虑到用户是初学者,在回答时要避免过于底层的寄存器操作,而是用通用流程配合代码示例。准备用STM32 HAL库作为示例,因为这类库最常用。同时要提醒用户注意平台差异性,比如缓存处理在带MMU的系统中更关键。 在相关问题上,用户可能后续会问到中断处理、性能优化等,所以提前准备几个延伸问题会很有帮助。特别是DMA的通道优先级(引用[3]提到)和错误处理,这些都是实际开发中的常见痛点。</think>DMA代码编写通常遵循**初始化→配置→启动传输→处理中断**的核心流程,以下是详细步骤和示例: --- ### **一、DMA代码编写通用流程** 1. **初始化DMA控制器** - 使能DMA时钟 - 获取DMA配置结构体 - 设置DMA实例(如通道、传输模式) ```c // STM32示例(HAL库) DMA_HandleTypeDef hdma; hdma.Instance = DMA1_Channel1; // 选择DMA通道 hdma.Init.Direction = DMA_MEMORY_TO_PERIPH; // 传输方向:内存→外设 hdma.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址不递增 hdma.Init.MemInc = DMA_MINC_ENABLE; // 内存地址递增 HAL_DMA_Init(&hdma); ``` 2. **配置传输参数** - 源地址(内存/外设寄存器) - 目标地址(外设寄存器/内存) - 传输数据量(字节/字) - 传输模式(单次/循环传输) ```c // 启动传输(以内存到UART为例) uint8_t buffer[100]; HAL_DMA_Start(&hdma, (uint32_t)buffer, (uint32_t)&huart1.TDR, 100); ``` 3. **处理传输完成/错误中断** - 使能DMA中断 - 实现中断回调函数 ```c // 中断服务函数中调用 void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma); // 传输完成回调函数(用户实现) void HAL_DMA_XferCpltCallback(DMA_HandleTypeDef *hdma) { // 释放资源或启动下一次传输 } ``` 4. **缓存一致性处理(高级系统需关注)** - 若使用CPU缓存,需在传输前后刷新缓存: ```c // ARM Cortex-M示例 SCB_CleanDCache_by_Addr((uint32_t*)buffer, sizeof(buffer)); // 写入前清理缓存 SCB_InvalidateDCache_by_Addr((uint32_t*)buffer, sizeof(buffer)); // 读取前失效缓存 ``` --- ### **二、关键注意事项** 1. **外设触发模式** - 外设触发传输时(如ADC采集),需配置DMA为外设到内存模式,并关联外设请求信号: ```c // 将DMA与ADC关联(STM32) __HAL_LINKDMA(&hadc, DMA_Handle, hdma_adc); HAL_ADC_Start_DMA(&hadc, (uint32_t*)adc_buffer, 100); ``` 2. **通道优先级与仲裁** - 多通道竞争时,通过`hdma.Init.Priority`设置优先级(参考引用[3]): ```c hdma.Init.Priority = DMA_PRIORITY_HIGH; // 高/中/低优先级 ``` 3. **传输模式选择** | 模式 | 特点 | 适用场景 | |---------------|--------------------------|-----------------------| | 单次传输 | 传输完成后自动停止 | 单次数据块搬运 | | 循环传输 | 传输完成后自动重置 | 持续采集(如音频) | | 双缓冲 | 交替使用两块内存 | 高吞吐无间断传输 | --- ### **三、完整流程示例(STM32内存到内存传输)** ```c // 1. 初始化 DMA_HandleTypeDef hdma_mem; hdma_mem.Instance = DMA1_Channel1; hdma_mem.Init.Mode = DMA_NORMAL; // 单次传输 HAL_DMA_Init(&hdma_mem); // 2. 启动传输(源→目标) uint32_t src[100], dest[100]; HAL_DMA_Start(&hdma_mem, (uint32_t)src, (uint32_t)dest, 100); // 3. 等待传输完成 HAL_DMA_PollForTransfer(&hdma_mem, HAL_DMA_FULL_TRANSFER, 1000); // 4. 或使用中断(需提前使能中断) // HAL_DMA_Start_IT(&hdma_mem, ...); ``` --- ### **四、不同平台的差异** 1. **ARM Cortex-M(如STM32)** - 依赖HAL/LL库简化配置 - 中断向量需绑定到DMA通道 2. **Zynq/AXI DMA(参考引用[2])** - 需初始化`XAxiDma`驱动 - 处理缓存同步(`Xil_DCacheFlush()`) - 支持Scatter-Gather(SG)模式 3. **LPC1700系列(参考引用[1])** - 直接操作寄存器配置源/目标地址 - 通过`GPDMA_Channel_Enable()`启动传输 > ⚠️ **核心原则**:DMA的本质是**硬件自动化搬运数据**,代码需确保三点: > ① 地址/长度配置正确; > ② 传输方向匹配硬件连接; > ③ 同步机制(中断/轮询)可靠[^4]。 ---
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值