在Linux 4.0下进行dmaengine的编程主要分为两部分,DMA Engine控制器编程和DMA Engine API编程。
DMA Engine API编程
slave DMA用法包括以下的步骤:
1. 分配一个DMA slave通道;
2. 设置slave和controller特定的参数;
3. 获取一个传输描述符;
4. 提交传输描述符;
5. 发起等待的请求并等待回调通知。
下面是以上每一步的详细说明。
1. 分配一个DMA slave通道
在slave DMA上下文通道的分配略有不同,客户端驱动通常需要一个通道,这个通道源自特定的DMA控制器,在某些情况甚至需要一个特定的通道。请求通道的API是channel dma_request_channel()。
其接口如下:
struct dma_chan *dma_request_channel(dma_cap_mask_t mask,
dma_filter_fn filter_fn,
void *filter_param);
1
2
3
其中dma_filter_fn接口定义如下:
typedef bool (*dma_filter_fn)(struct dma_chan *chan, void *filter_param);
1
filter_fn是可选的,但是对于slave和cyclic通道我们强烈推荐使用,因为它们需要获取一个特定的DMA通道。
当filter_fn参数为空,dma_request_channel()函数简单地返回第一个满足mask参数的通道。
否则,filter_fn函数将会对每个空闲的通道调用一次,同样这些通道也是要满足mask参数。当filter_fn返回true的时候说明期望的通道已经找到。
通过这个API分配的通道在dma_release_channel()函数调用前对于其他调用者是互斥的。
2. 设置slave和controller特定的参数
这一步通常是传递一些特定的信息到DMA驱动。大多数slave DMA使用到的通用信息都在结构体dma_slave_config中。它允许客户端对外设指定DMA的方向、DMA地址、总线宽度、DMA突发长度等等。
如果一些DMA控制器有更多要发送的参数,那它们应该试图把结构体dma_slave_config内嵌到控制器特定的结构体中。对于客户端来说将有更多的灵活性来传递更多需要的参数。
接口如下:
int dmaengine_slave_config(struct dma_chan *chan,
struct dma_slave_config *config)
1
2
对于dma_slave_config结构体成员的详解可在dmaengine.h中查看。
3. 获取一个传输描述符
DMA-engine支持多种slave传输模式:
- slave_sg:DMA一列聚散buffers from/to外设;
- dma_cyclic:实现一个循环的DMA操作 from/to外设,直到操作被停止;
- interleaved_dma:对于Slave客户端和M2M客户端都很常见。这种情况下驱动已知Slave设备FIFO的地址。可对dma_interleaved_template结构体的成员设置适当的值来表示多种类型的操作;
这个传输API的返回值就是一个传输描述符。
其接口如下:
struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_data_direction direction,
unsigned long flags);
struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic(
struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
size_t period_len, enum dma_data_direction direction);
struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma(
struct dma_chan *chan, struct dma_interleaved_template *xt,
unsigned long flags);
1
2
3
4
5
6
7
8
9
10
11
12
在调用dmaengine_prep_slave_sg()函数前外设驱动必须已经映射scatterlist,该映射必须保持直到DMA操作完成。
一般的步骤如下:
nr_sg = dma_map_sg(chan->device->dev, sgl, sg_len);
if (nr_sg == 0)
/* error */
desc = dmaengine_prep_slave_sg(chan, sgl, nr_sg, direction, flags);
1
2
3
4
5
一旦传输描述符获取成功,回调信息加入后描述符被提交。
注意:
对于Slave DMA,在回调函数被调用前接下来的传输描述符可能不能提交,因此slave DMA回调函数允许准备并提交一个新的传输描述符;
对于cyclic
DMA,回调函数可被用来停止DMA操作,通过在回调函数内部调用dmaengine_terminate_all()函数完成;
因此,在调用回调函数前DMA engine驱动解锁是非常重要的,因为它会导致死锁。
回调函数通常通过DMA engines的底半部tasklet方式调用,而不是直接通过中断顶半部调用。
4. 提交传输描述符
一旦传输描述符准备好并且回调函数也加入后,下一步就是把传输描述符加入到DMA engine驱动的等待队列。
其接口如下:
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)
1
返回值是一个cookie,主要用来检查DMA engine活动的状态过程,可通过其他的DMA engine API来实现。
dmaengine_submit()函数仅仅提交描述符到DMA engine的等待队列,它不会启动DMA操作。
5. 发起等待的请求并等待回调通知
等待队列里面的传输描述符可通过调用issue_pending API激活。此时如果通道是空闲的,等待队列中的第一个传输描述符将会启动DMA操作。
每次DMA操作完成后,等待队列中的下一个描述符将会启动DMA操作并且一个tasklet将会被处罚。如果我们在前面设定了客户端驱动的回调通知函数,那么tasklet将会调用这个函数。
其接口如下:
void dma_async_issue_pending(struct dma_chan *chan);
1
6. 其他API接口
dma engine API 说明
int dmaengine_terminate_all(struct dma_chan *chan) 指定DMA通道的所有活动都会被停止,DMA FIFO里面尚未传输完成的数据可能会丢失。未完成的DMA传输不会调用任何回调函数
int dmaengine_pause(struct dma_chan *chan) 指定DMA通道的活动将会被暂停,不会造成数据的丢失
int dmaengine_resume(struct dma_chan *chan) 指定DMA通道的活动将会被恢复
enum dma_status dma_async_is_tx_complete(struct dma_chan *chan,dma_cookie_t cookie, dma_cookie_t *last, dma_cookie_t *used) 这个函数用来检查指定DMA通道的状态。可在linux/dmaengine.h查看这个API更详细的用法,这个函数可与dma_async_is_complete()函数一并使用,从dmaengine_submit()函数返回的cookie可用来检查指定DMA传输是否完成。注意:不是所有的DMA engine驱动能在一个正在运行的DMA通道返回准确的信息。因此我们推荐用户在使用这个API前先暂停或者停止指定DMA通道。
DMA Engine controller编程
和其他的内核框架类似,dmaengine的注册依赖于填充一个结构并把它注册到框架中。对于dmaengine,这个结构是dma_device。
1. 分配dma_device结构
2. 初始化dma_device结构
dma_device结构的成员说明如下:
dma_device结构体成员 说明
channel 使用INIT_LIST_HEAD宏初始化
src_addr_widths 包含支持源传输宽度的bitmask
dst_addr_widths 包含支持目的传输宽度的bitmask
directions 包含一个支持从设备方向的bitmask
residue_granularity 使用dma_set_residue设置
Descriptor 你的设备不支持任何种类的residue报告。框架仅仅知道特定传输描述符完成
Segment 你的设备能报告哪个数据块完成传输
Burst 你的设备能报告哪一次突发传输已经完成
dev 保存指向device的指针,这个指针和你当前的驱动实例相关
3. 设置设备支持的传输类型
dma_device结构有一个域cap_mask,这个域保存支持的传输类型,可使用dma_cap_set()函数改变,支持的传输类型定义在dma_transaction_type中,它位于include/linux/dmaengine.h头文件中:
DMA传输类型 说明
DMA_MEMCPY 内存到内存的拷贝
DMA_SG 设备支持内存到内存的分散/聚合传输
DMA_INTERLEAVE 内存到内存的交错传输;交错传输的定义:传输数据从一个非连续的buffer到一个非连续的buffer,和DMA_SLAVE相反。通常用在2D内容的传输,在那种场景下你想直接传输一部分未经压缩的数据到显示部分
DMA_XOR 设备能在内存区域执行XOR操作,用来加速对XOR敏感的任务,例如RAID5
DMA_XOR_VAL 使用XOR进行内存Buffer的奇偶校验
DMA_PQ 内存到内存的P+Q 计算
DMA_PQ_VAL 设备能在内存buffer执行奇偶校验使用RAID6 P+Q算法
DMA_ INTERRUPT 设备能触发一个虚拟的传输,它将会产生一个中断
DMA_SLAVE 设备能处理设备到内存的传输,包括分散/聚合传输。在mem2mem情况下我们有两种传输类型:一种是单数据块拷贝,一种是数据块的集合拷贝。这里我们仅仅使用单传输类型来处理以上两种情况。如果你想传输一个单独的连续内存buffer,只需要简单建立一个scatter链表,这个链表仅有一个项
DMA_CYCLIC 设备能处理循环传输;循环传输的定义:数据块集合能循环遍历它自己,并且最后的项指向第一项。通常用在音频传输,操作一个环形buffer,你需要做的仅仅是往这个Buffer填充音频数据
DMA_PRIVATE 不通过dma_request_channel函数请求的通道进行异步发送,使用随机的信道进行传输
DMA_ASYNC_TX 不必由设备设置,如何需要将会由框架设置
以上的类型都将会影响源地址和目的地址如何随着时间如何改变。
4. 设备的操作
为了完成实际的逻辑,dma_device结构需要有一些函数指针,现在我们开始讨论有哪些操作我们可以实现的。我们必须填充的一些函数基于我们选择的传输类型。
DMA设备操作函数 说明
device_alloc_chan_resources 当client驱动调用dma_request_channel的时候将会调用device_alloc_chan_resources,负责分配通道需要的资源
device_free_chan_resources 当client驱动调用dma_release_channel的时候将会调用device_free_chan_resources,负责释放通道需要的资源
device_prep_dma_* 为DMA传输准备传输描述符
device_issue_pending 从pending queue中取走第一个传输描述符并启动传输。当传输完成后将会移到列表中的下一个传输描述符。这个函数可以在中断上下文中使用
device_tx_status 报告一个通道还有多少字节数据要传输
device_config 使用给定的参数重新配置通道
device_pause 暂停通道的传输
device_resume 恢复通道的传输
device_terminate_all 停止通道中所有的(包括pending的和正在进行的传输)传输
5. 注册DMA设备
int dma_async_device_register(struct dma_device *device)
利用这个函数把填充好的dma_device结构实体注册到内核中。
注册DMA控制器到DT DMA helpers
int of_dma_controller_register(struct device_node *np,
struct dma_chan *(*of_dma_xlate)
(struct of_phandle_args *, struct of_dma *),
void *data);
- @np: device node of DMA controller
- @of_dma_xlate: translation function which converts a phandle
- arguments list into a dma_chan structure
- @data pointer to controller specific data to be used by
- translation function
————————————————
版权声明:本文为CSDN博主「were0415」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/were0415/article/details/54095899
来源:https://blog.csdn.net/dragon101788/article/details/100150411