在Linux 4.0下进行dmaengine的编程主要分为两部分,DMA Engine控制器编程和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);
其中dma_filter_fn接口定义如下:
typedef bool (*dma_filter_fn)(struct dma_chan *chan, void *filter_param);
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)
对于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);
在调用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);
一旦传输描述符获取成功,回调信息加入后描述符被提交。
注意:
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)
返回值是一个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);
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通道。 |
和其他的内核框架类似,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结构实体注册到内核中。
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