前面文章介绍“ Linux MMC framework ”的时候,涉及到了MMC数据传输,进而不可避免地遭遇了DMA(Direct Memory Access)。因而,择日不如撞日,就开几篇文章介绍Linux的DMA Engine framework吧。
本文是DMA Engine framework分析文章的第一篇,主要介绍DMA controller的概念、术语(从硬件的角度,大部分翻译自kernel的document [1] )。之后,会分别从Provider(DMA controller驱动)和Consumer(其它驱动怎么使用DMA传输数据)两个角度,介绍Linux DMA engine有关的技术细节。
DMA是Direct Memory Access的缩写,顾名思义,就是绕开CPU直接访问memory的意思。在计算机中,相比CPU,memory和外设的速度是非常慢的,因而在memory和memory(或者memory和设备)之间搬运数据,非常浪费CPU的时间,造成CPU无法及时处理一些实时事件。因此,工程师们就设计出来一种专门用来搬运数据的器件----DMA控制器,协助CPU进行数据搬运,如下图所示:
图片1 DMA示意图
思路很简单,因而大多数的DMA controller都有类似的设计原则,归纳如下 [1] 。
注1:得益于类似的设计原则,Linux kernel才有机会使用一套framework去抽象DMA engine有关的功能。
2.1 DMA channels
一个DMA controller可以“同时”进行的DMA传输的个数是有限的,这称作DMA channels。当然,这里的channel,只是一个逻辑概念,因为:
鉴于总线访问的冲突,以及内存一致性的考量,从物理的角度看,不大可能会同时进行两个(及以上)的DMA传输。因而DMA channel不太可能是物理上独立的通道;
很多时候,DMA channels是DMA controller为了方便,抽象出来的概念,让consumer以为独占了一个channel,实际上所有channel的DMA传输请求都会在DMA controller中进行仲裁,进而串行传输;
因此,软件也可以基于controller提供的channel(我们称为“物理”channel),自行抽象更多的“逻辑”channel,软件会管理这些逻辑channel上的传输请求。实际上很多平台都这样做了,在DMA Engine framework中,不会区分这两种channel(本质上没区别)。
2.2 DMA request lines
由图片1的介绍可知,DMA传输是由CPU发起的:CPU会告诉DMA控制器,帮忙将xxx地方的数据搬到xxx地方。CPU发完指令之后,就当甩手掌柜了。而DMA控制器,除了负责怎么搬之外,还要决定一件非常重要的事情(特别是有外部设备参与的数据传输):
何时可以开始数据搬运?
因为,CPU发起DMA传输的时候,并不知道当前是否具备传输条件,例如source设备是否有数据、dest设备的FIFO是否空闲等等。那谁知道是否可以传输呢?设备!因此,需要DMA传输的设备和DMA控制器之间,会有几条物理的连接线(称作DMA request,DRQ),用于通知DMA控制器可以开始传输了。
这就是DMA request lines的由来,通常来说,每一个数据收发的节点(称作endpoint),和DMA controller之间,就有一条DMA request line(memory设备除外)。
最后总结:
DMA channel是Provider(提供传输服务),DMA request line是Consumer(消费传输服务)。在一个系统中DMA request line的数量通常比DMA channel的数量多,因为并不是每个request line在每一时刻都需要传输。
2.3 传输参数
在最简单的DMA传输中,只需为DMA controller提供一个参数----transfer size,它就可以欢快的工作了:
在每一个时钟周期,DMA controller将1byte的数据从一个buffer搬到另一个buffer,直到搬完“transfer size”个bytes即可停止。
不过这在现实世界中往往不能满足需求,因为有些设备可能需要在一个时钟周期中,传输指定bit的数据,例如:
memory之间传输数据的时候,希望能以总线的最大宽度为单位(32-bit、64-bit等),以提升数据传输的效率;
而在音频设备中,需要每次写入精确的16-bit或者24-bit的数据;
等等。
因此,为了满足这些多样的需求,我们需要为DMA controller提供一个额外的参数----transfer width。
另外,当传输的源或者目的地是memory的时候,为了提高效率,DMA controller不愿意每一次传输都访问memory,而是在内部开一个buffer,将数据缓存在自己buffer中:
memory是源的时候,一次从memory读出一批数据,保存在自己的buffer中,然后再一点点(以时钟为节拍),传输到目的地;
memory是目的地的时候,先将源的数据传输到自己的buffer中,当累计一定量的数据之后,再一次性的写入memory。
这种场景下,DMA控制器内部可缓存的数据量的大小,称作burst size----另一个参数。
2.4 scatter-gather
我们在“ Linux MMC framework(2)_host controller driver ”中已经提过scatter的概念,DMA传输时也有类似概念:
一般情况下,DMA传输一般只能处理在物理上连续的buffer。但在有些场景下,我们需要将一些非连续的buffer拷贝到一个连续buffer中(这样的操作称作scatter gather,挺形象的)。
对于这种非连续的传输,大多时候都是通过软件,将传输分成多个连续的小块(chunk)。但为了提高传输效率(特别是在图像、视频等场景中),有些DMA controller从硬件上支持了这种操作。
注2:具体怎么支持,和硬件实现有关,这里不再多说(只需要知道有这个事情即可,编写DMA controller驱动的时候,自然会知道怎么做)。
本文简单的介绍了DMA传输有关的概念、术语,接下来将会通过下面两篇文章,介绍Linux DMA engine有关的实现细节:
Linux DMA Engine framework(2)_provider
Linux DMA Engine framework(3)_consumer
本文将从provider的角度,介绍怎样在linux kernel dmaengine的框架下,编写dma controller驱动。
设备驱动的本质是描述并抽象硬件,然后为consumer提供操作硬件的友好接口。dma controller驱动也不例外,它要做的事情无外乎是:
1)抽象并控制DMA控制器。
2)管理DMA channel(可以是物理channel,也可以是虚拟channel,具体可参考[1]中的介绍),并向client driver提供友好、易用的接口。
3)以DMA channel为操作对象,响应client driver(consumer)的传输请求,并控制DMA controller,执行传输。
当然,按照惯例,为了统一提供给consumer的API(参考[2]),并减少DMA controller driver的开发难度(从论述题变为填空题),dmaengine framework提供了一套controller driver的开发框架,主要思路是(参考图片1):
图片1 DMA驱动框架
1)使用struct dma_device抽象DMA controller,controller driver只要填充该结构中必要的字段,就可以完成dma controller的驱动开发。
2)使用struct dma_chan(图片1中的DCn)抽象物理的DMA channel(图片1中的CHn),物理channel和controller所能提供的通道数一一对应。
3)基于物理的DMA channel,使用struct virt_dma_cha抽象出虚拟的dma channel(图片1中的VCx)。多个虚拟channel可以共享一个物理channel,并在这个物理channel上进行分时传输。
4)基于这些数据结构,提供一些便于controller driver开发的API,供driver使用。
上面三个数据结构的描述,可参考第3章的介绍。然后,我们会在第4章介绍相关的API、controller driver的开发思路和步骤以及dmaengine中和controller driver有关的重要流程。
3.1 struct dma_device
用于抽象dma controller的struct dma_device是一个庞杂的数据结构(具体可参考include/linux/dmaengine.h中的代码),不过真正需要dma controller driver关心的内容却不是很多,主要包括:
注1:为了加快对dmaengine framework的理解和掌握,这里只描述一些简单的应用场景,更复杂的场景,只有等到有需求的时候,再更深入的理解。
channels,一个链表头,用于保存该controller支持的所有dma channel(struct dma_chan,具体可参考3.2小节)。在初始化的时候,dma controller driver首先要调用INIT_LIST_HEAD初始化它,然后调用list_add_tail将所有的channel添加到该链表头中。
cap_mask,一个bitmap,用于指示该dma controller所具备的能力(可以进行什么样的DMA传输),例如(具体可参考enum dma_transaction_type的定义):
DMA_MEMCPY,可进行memory copy;
DMA_MEMSET,可进行memory set;
DMA_SG,可进行scatter list传输;
DMA_CYCLIC,可进行cyclic类[2]的传输;
DMA_INTERLEAVE,可进行交叉传输[2];
等等,等等(各种奇奇怪怪的传输类型,不看不知道,一看吓一跳!!)。
另外,该bitmap的定义,需要和后面device_prep_dma_xxx形式的回调函数对应(bitmap中支持某个传输类型,就必须提供该类型对应的回调函数)。src_addr_widths,一个bitmap,表示该controller支持哪些宽度的src类型,包括1、2、3、4、8、16、32、64(bytes)等(具体可参考enum dma_slave_buswidth 的定义)。
dst_addr_widths,一个bitmap,表示该controller支持哪些宽度的dst类型,包括1、2、3、4、8、16、32、64(bytes)等(具体可参考enum dma_slave_buswidth 的定义)。directions,一个bitmap,表示该controller支持哪些传输方向,包括DMA_MEM_TO_MEM、DMA_MEM_TO_DEV、DMA_DEV_TO_MEM、DMA_DEV_TO_DEV,具体可参考enum dma_transfer_direction的定义和注释,以及[2]中相关的说明。
max_burst,支持的最大的burst传输的size。有关burst传输的概念可参考[1]。
descriptor_reuse,指示该controller的传输描述可否可重复使用(client driver可只获取一次传输描述,然后进行多次传输)。
device_alloc_chan_resources/device_free_chan_resources,client driver申请/释放[2] dma channel的时候,dmaengine会调用dma controller driver相应的alloc/free回调函数,以准备相应的资源。具体要准备哪些资源,则需要dma controller driver根据硬件的实际情况,自行决定(这就是dmaengine framework的流氓之处,呵呵~)。
device_prep_dma_xxx,同理,client driver通过dmaengine_prep_xxx API获取传输描述符的时候,damengine则会直接回调dma controller driver相应的device_prep_dma_xxx接口。至于要在这些回调函数中做什么事情,dma controller driver自己决定就是了(真懒啊!)。
device_config,client driver调用dmaengine_slave_config[2]配置dma channel的时候,dmaengine会调用该回调函数,交给dma controller driver处理。
device_pause/device_resume/device_terminate_all,同理,client driver调用dmaengine_pause、dmaengine_resume、dmaengine_terminate_xxx等API的时候,dmaengine会调用相应的回调函数。
device_issue_pending,client driver调用dma_async_issue_pending启动传输的时候,会调用调用该回调函数。
总结:dmaengine对dma controller的抽象和封装,只是薄薄的一层:仅封装出来一些回调函数,由dma controller driver实现,被client driver调用,dmaengine本身没有太多的操作逻辑。
3.2 struct dma_chan
struct dma_chan用于抽象dma channel,其内容为:
struct dma_chan { /* sysfs */ struct list_head device_node; /* DMA router */ void *private; |
需要dma controller driver关心的字段包括:
device,指向该channel所在的dma controller。
cookie,client driver以该channel为操作对象获取传输描述符时,dma controller driver返回给client的最后一个cookie。
completed_cookie,在这个channel上最后一次完成的传输的cookie。dma controller driver可以在传输完成时调用辅助函数dma_cookie_complete设置它的value。
device_node,链表node,用于将该channel添加到dma_device的channel列表中。
router、route_data,TODO。
3.3 struct virt_dma_cha
struct virt_dma_chan用于抽象一个虚拟的dma channel,多个虚拟channel可以共用一个物理channel,并由软件调度多个传输请求,将多个虚拟channel的传输串行地在物理channel上完成。该数据结构的定义如下:
/* drivers/dma/virt-dma.h */ struct virt_dma_desc { struct virt_dma_chan { spinlock_t lock; /* protected by vc.lock */ struct virt_dma_desc *cyclic; }; |
chan,一个struct dma_chan类型的变量,用于和client driver打交道(屏蔽物理channel和虚拟channel的差异)。
task,一个tasklet,用于等待该虚拟channel上传输的完成(由于是虚拟channel,传输完成与否只能由软件判断)。
desc_allocated、desc_submitted、desc_issued、desc_completed,四个链表头,用于保存不同状态的虚拟channel描述符(struct virt_dma_desc,仅仅对struct dma_async_tx_descriptor[2]做了一个简单的封装)。
damengine直接向dma controller driver提供的API并不多(大部分的逻辑交互都位于struct dma_device结构的回调函数中),主要包括:
1)struct dma_device变量的注册和注销接口
/* include/linux/dmaengine.h */ int dma_async_device_register(struct dma_device *device); void dma_async_device_unregister(struct dma_device *device); |
dma controller driver准备好struct dma_device变量后,可以调用dma_async_device_register将它(controller)注册到kernel中。该接口会对device指针进行一系列的检查,然后对其做进一步的初始化,最后会放在一个名称为dma_device_list的全局链表上,以便后面使用。
dma_async_device_unregister,注销接口。
2)cookie有关的辅助接口,位于“drivers/dma/dmaengine.h”中,包括
static inline void dma_cookie_init(struct dma_chan *chan) static inline dma_cookie_t dma_cookie_assign(struct dma_async_tx_descriptor *tx) static inline void dma_cookie_complete(struct dma_async_tx_descriptor *tx) static inline enum dma_status dma_cookie_status(struct dma_chan *chan, dma_cookie_t cookie, struct dma_tx_state *state) |
由于cookie有关的操作,有很多共性,dmaengine就提供了一些通用实现:
void dma_cookie_init,初始化dma channel中的cookie、completed_cookie字段。
dma_cookie_assign,为指针的传输描述(tx)分配一个cookie。
dma_cookie_complete,当某一个传输(tx)完成的时候,可以调用该接口,更新该传输所对应channel的completed_cookie字段。
dma_cookie_status,获取指定channel(chan)上指定cookie的传输状态。
3)依赖处理接口
void dma_run_dependencies(struct dma_async_tx_descriptor *tx); |
由前面的描述可知,client可以同时提交多个具有依赖关系的dma传输。因此当某个传输结束的时候,dma controller driver需要检查是否有依赖该传输的传输,如果有,则传输之。这个检查并传输的过程,可以借助该接口进行(dma controller driver只需调用即可,省很多事)。
4)device tree有关的辅助接口
extern struct dma_chan *of_dma_simple_xlate(struct of_phandle_args *dma_spec, struct of_dma *ofdma); extern struct dma_chan *of_dma_xlate_by_chan_id(struct of_phandle_args *dma_spec, struct of_dma *ofdma); |
上面两个接口可用于将client device node中有关dma的字段解析出来,并获取对应的dma channel。后面实际开发的时候会举例说明。
5)虚拟dma channel有关的API
后面会有专门的文章介绍虚拟dma,这里不再介绍。
上面啰嗦了这么多,相信大家还是似懂非懂(很正常,我也是,dmaengine framework特点就是框架简单,细节复杂)。到底怎么在dmaengine的框架下编写dma controller驱动呢?现在看来,只靠这篇文章,可能达不到目的了,这里先罗列一下基本步骤,后续我们会结合实际的开发过程,进一步的理解和掌握。
编写一个dma controller driver的基本步骤包括(不考虑虚拟channel的情况):
1)定义一个struct dma_device变量,并根据实际的硬件情况,填充其中的关键字段。
2)根据controller支持的channel个数,为每个channel定义一个struct dma_chan变量,进行必要的初始化后,将每个channel都添加到struct dma_device变量的channels链表中。
3)根据硬件特性,实现struct dma_device变量中必要的回调函数(device_alloc_chan_resources/device_free_chan_resources、device_prep_dma_xxx、device_config、device_issue_pending等等)。
4)调用dma_async_device_register将struct dma_device变量注册到kernel中。
5)当client driver申请dma channel时(例如通过device tree中的dma节点获取),dmaengine core会调用dma controller driver的device_alloc_chan_resources函数,controller driver需要在这个接口中奖该channel的资源准备好。
6)当client driver配置某个dma channel时,dmaengine core会调用dma controller driver的device_config函数,controller driver需要在这个函数中将client想配置的内容准备好,以便进行后续的传输。
7)client driver开始一个传输之前,会把传输的信息通过dmaengine_prep_slave_xxx接口交给controller driver,controller driver需要在对应的device_prep_dma_xxx回调中,将这些要传输的内容准备好,并返回给client driver一个传输描述符。
8)然后,client driver会调用dmaengine_submit将该传输提交给controller driver,此时dmaengine会调用controller driver为每个传输描述符所提供的tx_submit回调函数,controller driver需要在这个函数中将描述符挂到该channel对应的传输队列中。
9)client driver开始传输时,会调用dma_async_issue_pending,controller driver需要在对应的回调函数(device_issue_pending)中,依次将队列上所有的传输请求提交给硬件。
10)等等。
从我们的直观感受来说,DMA并不是一个复杂的东西,要做的事情也很单纯直白。因此Linux kernel对它的抽象和实现,也应该简洁、易懂才是。不过现实却不甚乐观(个人感觉),Linux kernel dmaengine framework的实现,真有点晦涩的感觉。为什么会这样呢?
如果一个软件模块比较复杂、晦涩,要么是设计者的功力不够,要么是需求使然。当然,我们不敢对Linux kernel的那些大神们有丝毫怀疑和不敬,只能从需求上下功夫了:难道Linux kernel中的driver对DMA的使用上,有一些超出了我们日常的认知范围?
要回答这些问题并不难,将dmaengine framework为consumers提供的功能和API梳理一遍就可以了,这就是本文的目的。当然,也可以借助这个过程,加深对DMA的理解,以便在编写那些需要DMA传输的driver的时候,可以更游刃有余。
从方向上来说,DMA传输可以分为4类:memory到memory、memory到device、device到memory以及device到device。Linux kernel作为CPU的代理人,从它的视角看,外设都是slave,因此称这些有device参与的传输(MEM2DEV、DEV2MEM、DEV2DEV)为Slave-DMA传输。而另一种memory到memory的传输,被称为Async TX。
为什么强调这种差别呢?因为Linux为了方便基于DMA的memcpy、memset等操作,在dma engine之上,封装了一层更为简洁的API(如下面图片1所示),这种API就是Async TX API(以async_开头,例如async_memcpy、async_memset、async_xor等)。
图片1 DMA Engine API示意图
最后,因为memory到memory的DMA传输有了比较简洁的API,没必要直接使用dma engine提供的API,最后就导致dma engine所提供的API就特指为Slave-DMA API(把mem2mem剔除了)。
本文主要介绍dma engine为consumers提供的功能和API,因此就不再涉及Async TX API了(具体可参考本站后续的文章。
注1:Slave-DMA中的“slave”,指的是参与DMA传输的设备。而对应的,“master”就是指DMA controller自身。一定要明白“slave”的概念,才能更好的理解kernel dma engine中有关的术语和逻辑。
注2:本文大部分内容翻译自kernel document[1],喜欢读英语的读者可以自行参考。
对设备驱动的编写者来说,要基于dma engine提供的Slave-DMA API进行DMA传输的话,需要如下的操作步骤:
1)申请一个DMA channel。
2)根据设备(slave)的特性,配置DMA channel的参数。
3)要进行DMA传输的时候,获取一个用于识别本次传输(transaction)的描述符(descriptor)。
4)将本次传输(transaction)提交给dma engine并启动传输。
5)等待传输(transaction)结束。
然后,重复3~5即可。
上面5个步骤,除了3有点不好理解外,其它的都比较直观易懂,具体可参考后面的介绍。
3.1 申请DMA channel
任何consumer(文档[1]中称作client,也可称作slave driver,意思都差不多,不再特意区分)在开始DMA传输之前,都要申请一个DMA channel(有关DMA channel的概念,请参考[2]中的介绍)。
DMA channel(在kernel中由“struct dma_chan”数据结构表示)由provider(或者是DMA controller)提供,被consumer(或者client)使用。对consumer来说,不需要关心该数据结构的具体内容(我们会在dmaengine provider的介绍中在详细介绍)。
consumer可以通过如下的API申请DMA channel:
struct dma_chan *dma_request_chan(struct device *dev, const char *name); |
该接口会返回绑定在指定设备(dev)上名称为name的dma channel。dma engine的provider和consumer可以使用device tree、ACPI或者struct dma_slave_map类型的match table提供这种绑定关系,具体可参考XXXX章节的介绍。
最后,申请得到的dma channel可以在不需要使用的时候通过下面的API释放掉:
void dma_release_channel(struct dma_chan *chan); |
3.2 配置DMA channel的参数
driver申请到一个为自己使用的DMA channel之后,需要根据自身的实际情况,以及DMA controller的能力,对该channel进行一些配置。可配置的内容由struct dma_slave_config数据结构表示(具体可参考4.1小节的介绍)。driver将它们填充到一个struct dma_slave_config变量中后,可以调用如下API将这些信息告诉给DMA controller:
int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config) |
3.3 获取传输描述(tx descriptor)
DMA传输属于异步传输,在启动传输之前,slave driver需要将此次传输的一些信息(例如src/dst的buffer、传输的方向等)提交给dma engine(本质上是dma controller driver),dma engine确认okay后,返回一个描述符(由struct dma_async_tx_descriptor抽象)。此后,slave driver就可以以该描述符为单位,控制并跟踪此次传输。
struct dma_async_tx_descriptor数据结构可参考4.2小节的介绍。根据传输模式的不同,slave driver可以使用下面三个API获取传输描述符(具体可参考Documentation/dmaengine/client.txt[1]中的说明):
struct dma_async_tx_descriptor *dmaengine_prep_slave_sg( struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic( struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma( |
dmaengine_prep_slave_sg用于在“scatter gather buffers”列表和总线设备之间进行DMA传输,参数如下:
注3:有关scatterlist 我们在[3][2]中有提及,后续会有专门的文章介绍它,这里暂且按下不表。
chan,本次传输所使用的dma channel。
sgl,要传输的“scatter gather buffers”数组的地址;
sg_len,“scatter gather buffers”数组的长度。direction,数据传输的方向,具体可参考enum dma_data_direction (include/linux/dma-direction.h)的定义。
flags,可用于向dma controller driver传递一些额外的信息,包括(具体可参考enum dma_ctrl_flags中以DMA_PREP_开头的定义):
DMA_PREP_INTERRUPT,告诉DMA controller driver,本次传输完成后,产生一个中断,并调用client提供的回调函数(可在该函数返回后,通过设置struct dma_async_tx_descriptor指针中的相关字段,提供回调函数,具体可参考4.2小节的介绍);
DMA_PREP_FENCE,告诉DMA controller driver,后续的传输,依赖本次传输的结果(这样controller driver就会小心的组织多个dma传输之间的顺序);
DMA_PREP_PQ_DISABLE_P、DMA_PREP_PQ_DISABLE_Q、DMA_PREP_CONTINUE,PQ有关的操作,TODO。
dmaengine_prep_dma_cyclic常用于音频等场景中,在进行一定长度的dma传输(buf_addr&buf_len)的过程中,每传输一定的byte(period_len),就会调用一次传输完成的回调函数,参数包括:
chan,本次传输所使用的dma channel。
buf_addr、buf_len,传输的buffer地址和长度。
period_len,每隔多久(单位为byte)调用一次回调函数。需要注意的是,buf_len应该是period_len的整数倍。
direction,数据传输的方向。
dmaengine_prep_interleaved_dma可进行不连续的、交叉的DMA传输,通常用在图像处理、显示等场景中,具体可参考struct dma_interleaved_template结构的定义和解释(这里不再详细介绍,需要用到的时候,再去学习也okay)。
3.4 启动传输
通过3.3章节介绍的API获取传输描述符之后,client driver可以通过dmaengine_submit接口将该描述符放到传输队列上,然后调用dma_async_issue_pending接口,启动传输。
dmaengine_submit的原型如下:
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc) |
参数为传输描述符指针,返回一个唯一识别该描述符的cookie,用于后续的跟踪、监控。
dma_async_issue_pending的原型如下:
void dma_async_issue_pending(struct dma_chan *chan); |
参数为dma channel,无返回值。
注4:由上面两个API的特征可知,kernel dma engine鼓励client driver一次提交多个传输,然后由kernel(或者dma controller driver)统一完成这些传输。
3.5 等待传输结束
传输请求被提交之后,client driver可以通过回调函数获取传输完成的消息,当然,也可以通过dma_async_is_tx_complete等API,测试传输是否完成。不再详细说明了。
最后,如果等不及了,也可以使用dmaengine_pause、dmaengine_resume、dmaengine_terminate_xxx等API,暂停、终止传输,具体请参考kernel document[1]以及source code。
4.1 struct dma_slave_config
中包含了完成一次DMA传输所需要的所有可能的参数,其定义如下:
/* include/linux/dmaengine.h */ struct dma_slave_config { enum dma_transfer_direction direction; phys_addr_t src_addr; phys_addr_t dst_addr; enum dma_slave_buswidth src_addr_width; enum dma_slave_buswidth dst_addr_width; u32 src_maxburst; u32 dst_maxburst; bool device_fc; unsigned int slave_id; }; |
direction,指明传输的方向,包括(具体可参考enum dma_transfer_direction的定义和注释):
DMA_MEM_TO_MEM,memory到memory的传输;
DMA_MEM_TO_DEV,memory到设备的传输;
DMA_DEV_TO_MEM,设备到memory的传输;
DMA_DEV_TO_DEV,设备到设备的传输。
注5:controller不一定支持所有的DMA传输方向,具体要看provider的实现。
注6:参考第2章的介绍,MEM to MEM的传输,一般不会直接使用dma engine提供的API。src_addr,传输方向是dev2mem或者dev2dev时,读取数据的位置(通常是固定的FIFO地址)。对mem2dev类型的channel,不需配置该参数(每次传输的时候会指定);
dst_addr,传输方向是mem2dev或者dev2dev时,写入数据的位置(通常是固定的FIFO地址)。对dev2mem类型的channel,不需配置该参数(每次传输的时候会指定);
src_addr_width、dst_addr_width,src/dst地址的宽度,包括1、2、3、4、8、16、32、64(bytes)等(具体可参考enum dma_slave_buswidth 的定义)。src_maxburst、dst_maxburst,src/dst最大可传输的burst size(可参考[2]中有关burst size的介绍),单位是src_addr_width/dst_addr_width(注意,不是byte)。
device_fc,当外设是Flow Controller(流控制器)的时候,需要将该字段设置为true。CPU中有关DMA和外部设备之间连接方式的设计中,决定DMA传输是否结束的模块,称作flow controller,DMA controller或者外部设备,都可以作为flow controller,具体要看外设和DMA controller的设计原理、信号连接方式等,不在详细说明(感兴趣的同学可参考[4]中的介绍)。
slave_id,外部设备通过slave_id告诉dma controller自己是谁(一般和某个request line对应)。很多dma controller并不区分slave,只要给它src、dst、len等信息,它就可以进行传输,因此slave_id可以忽略。而有些controller,必须清晰地知道此次传输的对象是哪个外设,就必须要提供slave_id了(至于怎么提供,可dma controller的硬件以及驱动有关,要具体场景具体对待)。
4.2 struct dma_async_tx_descriptor
传输描述符用于描述一次DMA传输(类似于一个文件句柄)。client driver将自己的传输请求通过3.3中介绍的API提交给dma controller driver后,controller driver会返回给client driver一个描述符。
client driver获取描述符后,可以以它为单位,进行后续的操作(启动传输、等待传输完成、等等)。也可以将自己的回调函数通过描述符提供给controller driver。
传输描述符的定义如下:
struct dma_async_tx_descriptor { dma_cookie_t cookie; enum dma_ctrl_flags flags; /* not a 'long' to pack with cookie */ dma_addr_t phys; struct dma_chan *chan; dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx); int (*desc_free)(struct dma_async_tx_descriptor *tx); dma_async_tx_callback callback; void *callback_param; struct dmaengine_unmap_data *unmap; #ifdef CONFIG_ASYNC_TX_ENABLE_CHANNEL_SWITCH struct dma_async_tx_descriptor *next; struct dma_async_tx_descriptor *parent; spinlock_t lock; #endif }; |
cookie,一个整型数,用于追踪本次传输。一般情况下,dma controller driver会在内部维护一个递增的number,每当client获取传输描述的时候(参考3.3中的介绍),都会将该number赋予cookie,然后加一。
注7:有关cookie的使用场景,我们会在后续的文章中再详细介绍。flags, DMA_CTRL_开头的标记,包括:
DMA_CTRL_REUSE,表明这个描述符可以被重复使用,直到它被清除或者释放;
DMA_CTRL_ACK,如果该flag为0,表明暂时不能被重复使用。phys,该描述符的物理地址??不太懂!
chan,对应的dma channel。
tx_submit,controller driver提供的回调函数,用于把改描述符提交到待传输列表。通常由dma engine调用,client driver不会直接和该接口打交道。
desc_free,用于释放该描述符的回调函数,由controller driver提供,dma engine调用,client driver不会直接和该接口打交道。
callback、callback_param,传输完成的回调函数(及其参数),由client driver提供。
后面其它参数,client driver不需要关心,暂不描述了。