dma engine 相关代码分析
dma engine 相关代码分析
我们首先看一下内核
Documentation
关于DMA相关描述的文档,
Documentation/dmaengine/client.txt
是DMA引擎API指南,是我们主要关心的使用指南;
Documentation/dmaengine/provider.txt
是DMA引擎控制器文档,我们只做了解即可。
DMA引擎API指南
注意:对于在async_tx
中使用DMA引擎,请参阅: Documentation/crypto/async-tx-api.txt
从DMA使用包括以下步骤:
- 申请从DMA通道
- 设置从和控制器的具体参数
- 获取事务的描述符
- 提交事务
- 发出挂起的请求并等待回调通知
申请从DMA通道
通道分配在从DMA上下文中略有不同,客户驱动程序通常只需要从一个特定的DMA控制器的通道,甚至在某些情况下需要一个特定的通道。
为了请求通道,使用了dma_request_chan()
API。
接口:
struct dma_chan *dma_request_chan(struct device *dev, const char *name);
它将找到并返回与dev
设备关联的name
DMA通道。通过DT
, ACPI
或基础板级文件dma_slave_map
匹配表进行匹配。
在调用dma_release_channel()
之前,通过这个接口分配的通道是独占的。
设置从设备和控制器的具体参数
下一步总是将一些特定的信息传递给DMA驱动程序。从DMA可以使用的大多数通用信息都在struct dma_slave_config
中。这允许客户为外设指定DMA方向,DMA地址,总线宽度,DMA突发长度等。
如果一些DMA控制器有更多的参数要发送,那么它们应该尝试在它们的控制器特定结构中嵌入struct dma_slave_config
。这为客户端提供了在需要时传递更多参数的灵活性。
接口:
int dmaengine_slave_config(struct dma_chan *chan,
struct dma_slave_config *config)
请参阅dmaengine.h
中的dma_slave_config
结构定义,以获得结构成员的详细说明。请注意,direction
成员将消失,因为它与准备发起调用中给出的指导重复。
获取事务的描述符
对于从属使用,DMA引擎支持各种从属传输模式:
- slave_sg : DMA来自/到外设的分散收集缓冲区列表
- dma_cyclic : 从/向外围设备执行循环DMA操作,直到操作明确停止为止。
- interleaved_dma : 这对于从属以及M2M客户端都是常见的。 对于设备的从属地址,驱动程序可能已经知道fifo。 通过为
dma_interleaved_template
成员设置适当的值,可以表示各种类型的操作。
此传输API的非NULL返回表示给定事务的“描述符”。
接口:
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()
之前,外围设备驱动程序应该已为DMA操作映射了分散列表,并且必须保持该分散列表已映射,直到DMA操作完成为止。 散列表必须使用DMA结构设备进行映射。 如果以后需要同步映射,则也必须使用DMA结构设备调用dma_sync_*_for_*()
。
因此,正常设置应如下所示:
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引擎驱动程序可能会在成功准备和提交之间保持自旋锁,因此,将这两个操作紧密配对非常重要。
注意:
尽管
async_tx
API指定完成回调例程无法提交任何新操作,但从属/循环DMA并非如此。对于从DMA,在调用回调函数之前,后续事务可能无法提交,因此允许从DMA回调准备和提交新事务。
对于循环DMA,回调函数可能希望通过
dmaengine_terminate_async()
终止DMA。因此,重要的是DMA引擎驱动程序在调用可能导致死锁的回调函数之前放弃所有锁。
注意,回调将始终从DMA引擎tasklet调用,而不是从中断上下文调用。
提交事务
准备好描述符并添加回调信息后,必须将其放在DMA引擎驱动程序挂起队列中。
接口:
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)
这将返回一个cookie
,可用于通过本文档未涵盖的其他DMA引擎调用来检查DMA引擎活动的进度。
dmaengine_submit()
将不会启动DMA操作,它只会将其添加到挂起的队列中。 为此,请参阅步骤5,ma_async_issue_pending
。
发出挂起的请求并等待回调通知
可以通过调用issue_pending
API来**挂起队列中的事务。 如果通道空闲,则队列中的第一个事务开始,随后的队列排队。
完成每个DMA操作后,将启动下一个入队列并触发Tasklet。 然后,tasklet将调用客户端驱动程序完成回调例程进行通知(如果已设置)。
接口:
void dma_async_issue_pending(struct dma_chan *chan);
其他API
1. terminate
int dmaengine_terminate_sync(struct dma_chan *chan)
int dmaengine_terminate_async(struct dma_chan *chan)
int dmaengine_terminate_all(struct dma_chan *chan) /* DEPRECATED */
这将导致DMA通道的所有活动停止,并且可能会丢弃DMA FIFO中尚未完全传输的数据。 任何不完整的传输都不会调用回调函数。
此功能有两个变体。
dmaengine_terminate_async()
可能不会等到DMA完全停止或任何正在运行的完整回调完成后才开始。 但是可以从原子上下文或从完整的回调中调用dmaengine_terminate_async()
。 必须先调用dmaengine_synchronize()
,然后才能安全释放DMA传输访问的内存或释放从完整回调中访问的资源。
dmaengine_terminate_sync()
将等待传输以及所有正在运行的完整回调在返回之前完成。 但是,不得从原子上下文或完整的回调中调用该函数。
dmaengine_terminate_all()
已弃用,不应在新代码中使用。
2. pause
int dmaengine_pause(struct dma_chan *chan)
这将暂停DMA通道上的活动,而不会丢失数据。
3. resume
int dmaengine_resume(struct dma_chan *chan)
恢复先前暂停的DMA通道。 恢复当前未暂停的频道是无效的。
4. complete
enum dma_status dma_async_is_tx_complete(struct dma_chan *chan,
dma_cookie_t cookie, dma_cookie_t *last, dma_cookie_t *used)
这可用于检查通道的状态。 请参阅include/linux/dmaengine.h
中的文档以获取有关此API的更完整说明。
可以将其与dma_async_is_complete()
和dmaengine_submit()
返回的cookie
结合使用,以检查特定DMA事务的完成。
注意:
并非所有的DMA引擎驱动程序都可以为正在运行的DMA通道返回可靠的信息。 建议DMA引擎用户在使用此API之前暂停或停止(通过
dmaengine_terminate_all()
)通道。
5. synchronize
void dmaengine_synchronize(struct dma_chan *chan)
将DMA通道的终止于当前上下文同步。
此函数应在dmaengine_terminate_async()
之后使用,以将DMA通道的终止同步到当前上下文。 该函数将等待传输以及所有正在运行的完整回调在返回之前完成。
如果使用dmaengine_terminate_async()
停止DMA通道,则必须先调用此函数,然后才能安全释放先前提交的描述符访问的内存或释放先前提交的描述符的完整回调中访问的任何资源。
如果在dmaengine_terminate_async()
和此函数之间调用了dma_async_issue_pending()
,则此函数的行为是不确定的。
DMA引擎控制器文档
硬件介绍
大多数从DMA控制器具有相同的通用操作原理。
它们具有给定数量的通道用于DMA传输,以及给定数量的请求行。
请求和通道几乎是正交的。通道可用于为多个请求提供服务。为简化起见,通道是将要进行复制的实体,并请求涉及哪些端点。
请求线实际上对应于从DMA合格设备到控制器本身的物理线。每当设备将要开始传输时,它将通过声明该请求行来声明DMA请求(DRQ)。
一个非常简单的DMA控制器将只考虑一个参数:传输大小。在每个时钟周期,它将一个字节的数据从一个缓冲区传输到另一个缓冲区,直到达到传输大小为止。
由于从设备可能需要在单个周期内传输特定数量的位,因此在现实世界中效果不佳。例如,当执行简单的内存复制操作时,我们可能希望传输尽可能多的物理总线所允许的数据,但是我们的音频设备可能具有更窄的FIFO,要求一次将数据精确地写入16或24位。这就是为什么大多数(如果不是全部)DMA控制器都可以使用称为传输宽度的参数来进行调整的原因。
此外,无论何时将RAM用作源或目标,某些DMA控制器都可以将对内存的读取或写入分组到一个缓冲区中,因此您不必进行大量的小内存访问(这不是很有效),而是可以几个更大的转移。这是使用称为突发大小的参数完成的,该参数定义了在不将控制器拆分为较小的子传输的情况下允许执行的单次读取/写入的次数。
这样,理论上的DMA控制器将只能执行涉及单个连续数据块的传输。但是,我们通常不进行某些传输,并且希望将数据从非连续缓冲区复制到连续缓冲区,这称为分散收集。
至少对于mem2dev传输,DMAEngine需要支持分散收集。因此,这里有两种情况:要么我们有一个不支持它的非常简单的DMA控制器,而我们必须在软件中实现它,要么有一个更高级的DMA控制器,它以硬件分散性实现了-收集。
后者通常使用一组块进行编程以进行传输,并且每当传输开始时,控制器就会遍历该集合,并执行我们在此处进行的编程。
该集合通常是一个表或一个链表。然后,您将表的地址及其元素数或列表的第一项推入DMA控制器的一个通道,并且每当断言DRQ时,它将遍历该集合以知道在哪里获取数据来自。
无论哪种方式,此集合的格式都完全取决于您的硬件。每个DMA控制器将需要不同的结构,但是对于每个块,所有DMA控制器都至少需要源地址和目标地址,是否应增加这些地址以及我们前面看到的三个参数:突发大小,传输宽度和转印尺寸。
最后一件事是通常情况下,从设备默认情况下不会发出DRQ,并且每当您愿意使用DMA时,必须首先在从设备驱动程序中启用它。
这些只是一般的内存到内存(也称为mem2mem)或内存到设备(mem2dev)的传输。大多数设备通常支持dmaengine支持的其他类型的传输或内存操作,这将在本文档的后面进行详细介绍。
Linux中的DMA支持
从历史上看,DMA控制器驱动程序是使用异步TX API实现的,以卸载诸如内存复制,XOR,加密等之类的操作,基本上是任何内存到内存的操作。
随着时间的流逝,内存到设备传输的需求不断增加,并且dmaengine得以扩展。 如今,异步TX API被编写为dmaengine之上的一层,并充当客户端。 尽管如此,dmaengine在某些情况下仍可容纳该API,并做出了一些设计选择以确保其兼容。
有关Async TX API的更多信息,请参阅Documentation/crypto/async-tx-api.txt
中的相关文档文件。
DMA引擎注册
struct dma_device
初始化
与其他任何内核框架一样,整个DMAEngine注册都依赖于驱动程序填充结构并针对该框架进行注册。 在我们的例子中,该结构是dma_device
。
您需要在驱动程序中做的第一件事就是分配此结构。 任何常用的内存分配器都可以执行,但是您还需要在其中初始化一些字段:
- channels : 例如,应使用
INIT_LIST_HEAD
宏将其初始化为列表 - src_addr_widths : 应包含支持的源传输宽度的位掩码
- dst_addr_widths : 应包含支持的目标传输宽度的位掩码
- directions : 应该包含受支持的从站方向的位掩码(例如,不包括mem2mem传输)
- residue_granularity :
- 报告给dma_set_residue的转移残基的粒度。
- 可以是:
- 描述符(Descriptor) : 设备不支持任何类型的残留物报告。 框架将仅知道完成了特定的事务描述符。
- 区段(Segment):设备能够报告已传输的块
- 突发(Burst):设备能够报告已传输的突发
- dev : 应该持有指向与当前驱动程序实例关联的结构设备的指针。
支持的事务类型
接下来,您需要设置设备(和驱动程序)支持的事务类型。
我们的dma_device
结构具有一个名为cap_mask
的字段,该字段保存受支持的各种事务类型,您需要使用dma_cap_set
函数修改此掩码,并根据支持的事务类型将各种标志用作参数。
所有这些功能均在include/linux/dmaengine.h
中的dma_transaction_type
枚举中定义。
当前,可用的类型为:
- DMA_MEMCPY : 设备能够执行内存到内存拷贝。
- DMA_XOR : 该设备能够在存储区上执行XOR操作。用于加速XOR密集型任务,例如RAID5。
- DMA_XOR_VAL : 该设备能够使用XOR算法对内存缓冲区执行奇偶校验。
- DMA_PQ : 该设备能够执行RAID6 P + Q计算,P是简单的XOR,Q是Reed-Solomon算法。
- DMA_PQ_VAL : 该设备能够使用RAID6 P + Q算法针对内存缓冲区执行奇偶校验。
- DMA_INTERRUPT : 该设备能够触发虚拟传输,从而产生周期性中断。由客户端驱动程序用于注册回调,该回调将通过DMA控制器中断定期调用。
- DMA_SG : 该设备支持内存到内存的分散收集传输。尽管普通的memcpy看起来像是散点聚集转移的特殊情况,只有一个要转移的块,但是在mem2mem转移情况下,它是一种独特的事务类型。
- DMA_PRIVATE : 设备仅支持从属传输,因此不适用于异步传输。
- DMA_ASYNC_TX : 不得由设备设置,如果需要,将由框架设置。
- DMA_SLAVE : 设备可以处理设备到内存的传输,包括分散收集传输。在mem2mem情况下,我们有两种不同的类型来处理要复制的单个块或它们的集合,而在这里,我们只有一个应该处理这两种类型的事务类型。如果要传输单个连续的内存缓冲区,只需构建一个仅包含一项的分散列表。
- DMA_CYCLIC : 该设备可以处理循环传输。循环传输是块收集将在其自身上循环的传输,最后一项指向第一个。它通常用于音频传输,您要在单个环形缓冲区上进行操作,并在其中填充音频数据。
- DMA_INTERLEAVE : 设备支持交错传输。这些传输可以将数据从不连续的缓冲区传输到不连续的缓冲区,这与DMA_SLAVE相对,即将数据从不连续的数据集传输到连续的目标缓冲区。它通常用于2D内容传输,在这种情况下,您要将部分未压缩的数据直接传输到显示器以进行打印。
这些各种类型还将影响源地址和目标地址随时间变化的方式。
每次传输后,指向RAM的地址通常会增加(或增加)。 如果是环形缓冲区,它们可能会循环(DMA_CYCLIC)。 指向设备寄存器(例如FIFO)的地址通常是固定的。
设备操作
既然我们描述了我们能够执行的操作,我们的dma_device
结构还需要一些函数指针才能实现实际的逻辑。
我们必须在那里填充并因此必须实现的功能显然取决于您报告为受支持的事务类型。
- device_alloc_chan_resources/device_free_chan_resources
- 每当驱动程序在与该驱动程序关联的通道上第一次/最后一次调用dma_request_channel或dma_release_channel时,将调用这些函数。
- 他们负责分配/释放所有必需的资源,以使该通道对您的驱动程序有用。
- 这些功能可以休眠。
- device_prep_dma_*
- 这些功能与您先前注册的功能匹配。
- 这些函数全部采用与准备传输有关的缓冲区或分散列表,并应从中创建硬件描述符或硬件描述符列表。
- 可以从中断上下文中调用这些函数
- 您可能要做的任何分配都应使用GFP_NOWAIT标志,以免潜在进入睡眠状态,而又不会耗尽应急池。
- 驱动程序应尝试在探测时预先分配在传输设置过程中可能需要的所有内存,以避免给nowait分配器造成很大压力。
- 它应返回dma_async_tx_descriptor结构的唯一实例,该实例进一步表示此特定传输。
- 可以使用功能dma_async_tx_descriptor_init初始化此结构。
- 您还需要在此结构中设置两个字段:
- flags : 它可以由驱动程序本身修改,还是它应该始终是参数中传递的标志吗
- tx_submit : 指向必须实现的函数的指针,该函数应该将当前事务描述符推入挂起的队列,等待issue_pending被调用。
- 在这种结构中,可以初始化函数指针callback_result,以便通知提交者事务已完成。 在较早的代码中,已使用函数指针回调。 但是,它不为交易提供任何内容,因此将不推荐使用。 传递给callback_result的定义为dmaengine_result的结果结构具有两个字段:
- result : 这提供了由dmaengine_tx_result定义的传输结果。 成功或某些错误情况。
- residue : 为支持残留的对象提供传输的残留字节。
- device_issue_pending
- 获取挂起队列中的第一个事务描述符,然后开始传输。 无论何时完成转移,它都应移至列表中的下一个事务。
- 可以在中断上下文中调用此函数。
- device_tx_status
- 应该报告给定通道上剩余的字节数
- 只应关心作为参数传递的事务描述符,而不是给定通道上当前活动的事务描述符
- tx_state参数可能为NULL
- 应该使用dma_set_residue进行报告
- 如果是周期性传输,则应仅考虑当前期间。
- 可以在中断上下文中调用此函数。
- device_config
- 使用参数作为参数重新配置通道
- 此命令不应同步执行,也不应在当前排队的传输中执行,而只能在后续传输中执行
- 在这种情况下,该函数将接收dma_slave_config结构指针作为参数,该指针将详细说明要使用的配置。
- 即使该结构包含一个Direction字段,但不赞成使用此字段,而推荐给prep_ *函数使用direction参数
- 此调用仅对于从属操作是必需的。 不应为memcpy操作设置或期望将其设置为。 如果驱动程序同时支持这两种驱动程序,则应将此调用仅用于从属操作,而不用于memcpy操作。
- device_pause
- 暂停通道上的传输
- 该命令应在通道上同步运行,立即暂停给定通道的工作
- device_resume
- 恢复通道上的传输
- 该命令应在通道上同步运行,立即恢复给定通道的工作
- device_terminate_all
- 中止通道上所有待处理和正在进行的传输
- 对于中止的传输,不应调用完整的回调
- 可以从原子上下文或在描述符的完整回调中调用。 绝对不能休眠。 驱动程序必须能够正确处理此问题。
- 终止可能是异步的。 驱动程序不必等到当前活动的传输完全停止。 请参阅device_synchronize。
- device_synchronize
- 必须将通道的终止与当前上下文同步。
- 必须确保DMA控制器不再访问先前提交的描述符的内存。
- 必须确保先前提交的描述符的所有完整回调都已完成运行,并且没有一个计划运行。
- 可能休眠
杂项说明
- dma_run_dependencies
- 应该在异步TX传输结束时调用,在从属传输情况下可以忽略。
- 在将其标记为完成之前,请确保已运行相关操作。
- dma_cookie_t
- 它是DMA事务ID,将随着时间的推移而增加。
- 自从引入virt-dma并将其抽象化以来,它不再具有实际意义。
- DMA_CTRL_ACK
- 如果清除,则描述符只能由提供者重用,直到客户端确认收到,即有机会建立任何依赖关系链
- 可以通过调用async_tx_ack()来确认
- 如果设置,并不意味着描述符可以重用
- DMA_CTRL_REUSE
- 如果设置,描述符在完成后可以重用。 如果设置了此标志,则提供程序不应释放它。
- 应该通过调用设置DMA_CTRL_REUSE的dmaengine_desc_set_reuse()来准备描述符以供重用。
- dmaengine_desc_set_reuse() 仅在容量支持的通道上行可重用描述符时成功。
- 因此,如果设备驱动程序要在两次传输之间跳过ma_map_sg()和dma_unmap_sg()),则由于未使用DMA数据,它可以在传输完成后立即重新提交传输。
- 描述符可以通过几种方式释放
- 通过调用dmaengine_desc_clear_reuse()并提交最后的txn来清除DMA_CTRL_REUSE
- 明确调用dmaengine_desc_free(),只有在已设置DMA_CTRL_REUSE的情况下,此操作才能成功
- 终止通道
一般设计说明:
您将看到的大多数DMAEngine驱动程序都基于类似的设计,该处理程序在处理程序中处理传输中断的结束,但是将大多数工作推迟到Tasklet,包括在先前的传输结束时开始新的传输。
但是,这是一个效率很低的设计,因为延迟不仅是中断延迟,而且是Tasklet的调度延迟,这将使通道之间处于空闲状态,这将减慢全局传输速率。
您应该避免这种做法,而不是在tasklet中选择新的转移,而是将该部分移到中断处理程序中,以使空闲窗口更短(无论如何我们还是无法避免)。
代码分析
接下来就从dmatest.c
的代码开始着手分析。
申请从DMA通道
申请通道使用dma_request_channel()
函数进行申请,比如dmatest.c
里面的下面这段代码:
看一下函数原型,dma_request_channel()
是一个宏定义,最后调用的是__dma_request_channel()
函数,申请通道时遍历已注册设备的dma设备列表,并找到具体的候选者:
看一下find_candidate()
是如何寻找候选者的,先通过private_candidate()
找到候选通道,找到之后设置设置为私有,并尝试找到dma通道的父驱动模块:
再看一下private_candidate()
函数,主要就是匹配能力集以及遍历所有通道,并找到到空闲的通道,最后调用filter函数进行过滤,并返回找到的通道:
这个时候申请通道就完成了。
设置从设备和控制器的具体参数
在dmatest
这里不需要进行从设备和控制器的具体参数。
获取事务的描述符
这个时候需要将要传输的目的地址、源地址、传输长度等数据告诉申请到的通道,用来准备传输描述符,比如下面这个device_prep_dma_memcpy()
:
我们看一下这个函数具体是做了什么操作,这个函数是dma控制器注册时赋值的函数,当前控制器代码是海思提供的hiedmacv310
,对应的函数是hiedmac_prep_dma_memcpy()
,是控制器注册时候赋值的,后面再分析一**册的代码,这里看hiedmac_prep_dma_memcpy()
:
可以看到这个函数最后调用了vchan_tx_prep()
去准备一个标准的dma传输描述符:
然后dmatest.c
里面的就可以用这个描述符进行下一步了。
提交事务
接下来看一下dmatest
里面是如何进行提交事务的:
设置回调函数之后,使用tx_submit()
进行提交传输事务,这个tx_submit()
是在上一步的vchan_tx_prep()
里面准备好的vchan_tx_submit()
,可以看到这个函数实际并没有开始传输,只是把描述符增加到submitted
链表里:
发出挂起的请求并等待回调通知
发起请求
再看一下如何发起请求,直接调用dma_async_issue_pending()
函数:
我们看一下dma_async_issue_pending()
函数,调用的是控制器注册时的函数hiedmac_issue_pending()
:
我们看一下hiedmac_issue_pending()
是如何处理的,物理通道空闲时发起传输:
调用hiedmac_start()
获取物理通道后写寄存器发起传输,具体的写寄存器过程就不分析了。
中断返回及回调通知
上面交给硬件的dma控制器后,传输完成后就会调用到中断回调函数hiemdacv310_irq()
,主要关心的是下一个部分,报告传输完成部分:
我们看一下vchan_cookie_complete()
函数,报告描述符传输完成,保存cookie
后加到完成链表里,然后调度tasklet
:
dma_cookie_complete()
函数比较简单,记录完成的cookie
并清理当前cookie
:
再回来看一下tasklet
,这个是在vchan_init()
时注册的tasklet
任务,执行的函数是vchan_complete()
,主要工作是获取传输描述符的回调函数,并发起回调:
dmaengine_desc_get_callback()
函数比较简单,就是一个赋值过程,此时这里的tx->callback
是dmatest.c
里面的dmatest_callback()
函数:
再看dmaengine_desc_callback_invoke()
,就是调用注册的回调函数:
这时候,dmatest_callback()
函数被执行,等待队列被唤醒:
判断是否传输成功
回调完成后,再判断一下是否传输成功,如dmatest
示例:
dma_async_is_tx_complete()
轮询传输是否完成:
回调到hiedmac_tx_status()
,调用dma_cookie_status()
查询是否完成:
dma_cookie_status()
:根据返回的cookie值以及当前在使用cookie值、上一个完成的cookie值来判断传输状态:
判断逻辑如下图所示:每次传输完成后cookie
值都是+1的,应该就是根据返回的cookie以及上一个完成的cookie来判断。
停止传输
在dmatest
传输任务退出或者模块退出时,会调用终止传输的函数,比如模块退出时调用的:
dmaengine_terminate_all()
回调至hiedmac_terminate_all()
:
我们看一下hiedmac_terminate_all()
函数,除了将本模块的资源进行释放外,还调用hiedmac_free_txd_list()
函数将vchan
的资源进行释放:
看一下hiedmac_free_txd_list()
,将vchan
的所有描述符移进行释放处理:
再看一下vchan_get_all_descriptors()
获取全部描述符的过程,就是将所有链表内容移到到head
:
vchan_dma_desc_free_list()
就是删除节点并释放资源:
释放通道资源
在dmatest
模块移除的时候,会调用dma_release_channel()
进行通道的释放,如下图所示:
dma_release_channel()
调用dma_chan_put()
减少引用计数:
dma_chan_put()
回调至hiedmac_free_chan_resources()
:
hiedmac_free_chan_resources()
调用vchan_free_chan_resources()
的资源释放函数:
vchan_free_chan_resources()
获取通道的所有描述符后清除reuse标志,并对资源描述符进行释放:
至此,一个完整的传输过程代码算是分析完成了。
HIEDMAC控制器注册
驱动的注册
hiedmac
注册的是平台驱动,匹配表需要与设备树进行对应,否则驱动注册成功,但设备树解析失败的话控制器也没有办法正常使用。
设备树内容如下:
probe
探测函数
hiedmacv310_probe-1
:解析设备树以及设置DMA_MEMCPY
能力集。
看一下设备树解析的过程:
get_of_probe-1
:主要获取时钟、复位等,并对寄存器空间进行ioremap映射。
get_of_probe-2
:获取硬件终中断号并映射,以及获取硬件支持的dma通道数、dma请求数。
当前海思平台支持的dma通道数与dma请求数看下图:
hiedmacv310_probe-2
:设置DMA_CLAVE
能力集,申请中断,并注册中断回调为hiemdacv310_irq()
函数。涉及到的函数部分在上面已经分析过了,这里就不做分析了。
hiedmacv310_probe-3
:初始物理通道,并注册memcpy
,slave
虚拟通道。
看一**册虚拟设备的过程hiedmac_init_virt_channels()
函数,主要是调用vchan_init()
函数去初始化虚拟通道:
vchan_init()
函数主要是初始化一个tasklet,执行任务vchan_complete()
函数,函数具体内容上面有分析过:
至于dma_async_device_register()
函数,暂时就不细看了,注册设备后增加到dma_device_list
链表后:
那么代码就暂时分析到这里了。