02、DMA API 接口介绍
以下是DMA引擎使用步骤:
- 分配通道
选择并申请一个DMA通道,用于数据传输。 - 配置参数
根据硬件需求,设置DMA控制器的必要参数(如传输方向、数据大小等)。 - 准备描述符
创建并获取描述数据传输细节的"描述符"(包含源地址、目标地址等信息)。 - 提交任务
将配置好的描述符提交给DMA控制器,让硬件开始准备传输。 - 启动并等待
启动DMA传输,然后等待硬件完成操作并返回结果通知。
每个步骤都像流水线环节:先申请资源,再配置细节,最后执行并等待结果。
一、申请DMA channel
设备驱动程序在开始DMA数据传输前,必须先申请一个DMA通道。具体步骤如下:
- 申请通道 通过调用
dma_request_channel
函数申请通道:
C++
struct dma_chan *dma_request_channel(const dma_cap_mask_t *mask,
dma_filter_fn fn, void *fn_param);
1
2
2
- 释放通道 当不再需要通道时,调用
dma_release_channel
释放资源:
C++
void dma_release_channel(struct dma_chan *chan)
1
只需要传入之前申请到的通道指针即可完成释放。
整个流程就是:用设备信息申请通道 → 使用DMA → 使用完毕后释放通道。
二、配置DMA channel的参数
这一步引入了一个新的数据结构,struct dma_slave_config,它表示DMA从通道的运行时配置。这允许客户端为外围设备指定设置,如DMA方向、DMA地址、总线宽度、DMA突发长度等。
C
int dmaengine_slave_config(struct dma_chan *chan,
struct dma_slave_config *config)
1
2
2
三、获取传输描述(tx descriptor)
DMA传输就像一个自动搬运工,它在后台默默工作,不需要CPU一直盯着。当设备需要传输数据时,驱动程序会先给DMA控制器发指令,比如告诉它要搬什么数据、从哪搬到哪。DMA控制器收到指令后会生成一个"任务标签"(结构体dma_async_tx_descriptor)。驱动程序拿着这个标签,就能随时查看搬运任务的进度或控制它。
根据不同的需求,驱动程序可以选择以下指令来生成任务标签:
- 普通内存复制
device_prep_dma_memcpy()
:复制两块内存的数据。 - 分散数据合并传输
device_prep_dma_sg()
:把分散在内存各处的小块数据合并传输(比如把碎片数据拼成一块搬走)。 - 数据校验操作
device_prep_dma_xor()
:计算数据校验码(比如网络数据的错误检查)。device_prep_dma_xor_val()
:先算校验码再检查数据是否正确。device_prep_dma_pq()
:为RAID存储生成校验码(比如硬盘阵列的P/Q码)。device_prep_dma_pq_val()
:生成校验码并验证数据完整性。
- 内存填充
device_prep_dma_memset()
:把一块内存填满固定值(比如全0)。device_prep_dma_memset_sg()
:对分散的内存区域同时填充固定值。
- 设备与内存数据传输
device_prep_slave_sg()
:把设备(如硬盘/U盘)的数据分段读到内存,或把内存数据分段写入设备。 - 复杂模式传输
device_prep_interleaved_dma()
:灵活配置多通道或交错模式的数据传输(比如同时处理多个数据流)。
这些函数就像是DMA控制器的"任务生成器"。驱动程序根据需求选择对应函数,生成任务标签后就能启动数据搬运,并随时通过标签查看任务状态。
四、提交事务
获取传输描述符之后,client driver可以通过dmaengine_submit接口将该描述符放到传输队列上,然后调用dma_async_issue_pending接口,启动传输。
dmaengine_submit的原型如下:
C
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)
1
dma_async_issue_pending的原型如下:
C
void dma_async_issue_pending(struct dma_chan *chan);
1