当前位置:网站首页>拷贝方式之DMA

拷贝方式之DMA

2022-07-05 16:21:00 四季帆

1. DMA 的原理和实现

        DMA 的原理就是 CPU 将需要迁移的数据的位置告诉给 DMA,包括源地址,目的地址以及需要迁移的长度,然后启动 DMA 设备,DMA 设备收到命令之后,就去完成相应的操作,最后通过中断反馈给老板 CPU,结束。

        在实现 DMA 传输时,是 DMA 控制器掌控着总线,也就是说,这里会有一个控制权转让的问题,我们当然知道,计算机中最大的 BOSS 就是 CPU,这个 DMA 暂时掌管的总线控制权当前也是 CPU 赋予的,在 DMA 完成传输之后,会通过中断通知 CPU 收回总线控制权。

2. DMA传输过程

        一个完整的 DMA 传输过程必须经过 DMA 请求、DMA 响应、DMA 传输、DMA 结束这四个阶段。

        DMA 请求:CPU 对 DMA 控制器初始化,并向 I/O 接口发出操作命令,I/O 接口提出 DMA 请求

        DMA 响应:DMA 控制器对 DMA 请求判别优先级以及屏蔽位,向总线裁决逻辑提出总线请求,当 CPU 执行完成当前的总线周期之后即可释放总线控制权。此时,总线裁决逻辑输出总线应答,表示 DMA 已经就绪,通过 DMA 控制器通知 I/O 接口开始 DMA 传输。

        DMA 传输:在 DMA 控制器的引导下,在存储器和外设之间进行数据传送,在传送过程中不需要 CPU 的参与

        DMA 结束:当完成既定操作之后,DMA 控制器释放总线控制权,并向 I/O 接口发出结束信号,当 I/O 接口收到结束信号之后,一方面停止 I/O 设备的工作,另一方面向 CPU 提出中断请求,使 CPU 从不介入状态解脱,并执行一段检查本次 DMA 传输操作正确性的代码。最后带着本次操作的结果以及状态继续执行原来的程序。

        外设能够利用DMA搬移数据。如MMC,当它接收到数据时, 会把数据存储在自己的寄存器中,既可以通过CPU循环读取寄存器来获取这些数据(此时占用CPU时间); 也可以利用DMA把寄存器的数据搬移到内存, 当搬移一定的数量后,通知CPU,然后CPU再从内存中一次性读取(DMA搬移过程中,CPU可以干别的事情)。

        DMA 工作时,由 DMA 控制器向内存发起地址和控制信号,进行地址修改,对传输的数据进行统计,并以中断的形式通知 CPU 数据传输完成。

3. DMA 导致的问题

        DMA 不仅仅只会带来效率的提升,同样,它也会带来一些问题,最明显的就是缓存一致性问题。想象一下,现代的 CPU 都是自带一级缓存、二级缓存甚至是三级缓存,当 CPU 访问内存某个地址时,暂时先将新的值写入到缓存中,但是没有更新外部内存的数据,假设此时发生了 DMA 请求且操作的就是这一块在缓存中更新了而外部内存没有更新的内存地址,这样 DMA 读到的就是非最新数据;相同的,如果外部设备通过 DMA 将新值写入到内存中,但是 CPU 访问得到的确实缓存中的数据,这样也会导致拿到的不是最新的数据。

        为了能够正确进行 DMA 操作,必须进行必要的 Cache 操作,Cache 的操作主要分为 invalidate(作废)和 writeback(回写),有时候也会二者一同使用。如果 DMA 使用了 Cache,那么 Cache 一致性问题是必须要考虑的,解决的最简单的办法就是禁止 DMA 目标地址范围的 Cache 功能,但是这样会牺牲掉一定的性能。因此,在 DMA 是否使用 cache 的问题上,可以根据 DMA 缓冲区的期望保留时间长短来决策。DMA 被区分为:一致性 DMA 映射和流式 DMA 映射。

4. 解决缓存一致性

        一致性 DMA ,因为采用的是系统预留的一段 DMA 内存用于 DMA 操作,这一段内核在系统启动阶段就已经预留完毕,在 DMA 内存申请的过程中,可以直接禁止这一段预留内存的缓存。首先进行一个 ioremap_nocache 的映射,然后调用函数 dma_cache_wback_inv 保证缓存已经刷新到位之后,后面使用这一段内存时不存在一二级缓存;

        流式 DMA ,不能直接禁止缓存,因为流式 DMA 可以使用系统中的任意地址范围的地址,CPU 总不能将系统所有的地址空间都禁止缓存,这不科学,那么为了实现缓存一致性,流式 DMA 需要不断的对缓存进行失效操作,告诉 CPU 这一段缓存是不可信的,必须从内存中重新获取。一致性 DMA 就是直接将缓存禁止,而流式 DMA 则是将缓存失效刷新。

5. DMA channels

         一个DMA controller可以同时进行的DMA传输的个数是有限的,这称作DMA channels。它只是一个逻辑概念,因为鉴于总线访问的冲突, 以及内存一致性的考量, 这些Channels不可能真正的并行工作,而只能分时复用,DMA控制器充当仲裁角色。既然是分时复用,那么我们也可以基于某一个物理Channel,抽象出多个虚拟Channel,由软件负责仲裁,以决定在某一时刻由哪个虚拟Channel来使用物理Channel。

6. DMA request line

        DMA传输是由CPU发起的:CPU告诉DMA控制器,帮忙将xxx地方的数据搬到xxx地方。CPU发完指令之后,就去做其他事情了。而DMA控制器,除了负责怎么搬之外,还要决定何时开始数据搬运。

        因为CPU发起DMA传输的时候,并不知道当前是否具备传输条件,例如source设备是否有数据、dest设备的FIFO是否空闲等等。只有设备知道什么时候开始传输,因此需要DMA传输的设备和DMA控制器之间,会有几条物理的连接线(即DMA request,DRQ),用于通知DMA控制器可以开始传输了。

7. 编写DMA的设备驱动一般步骤如下

/* 1. 申请DMA通道 */
struct dma_chan *dma_request_channel(dma_cap_mask_t mask, dma_filter_fn filter_fn, void *filter_param);

/* 2. DMA通道的配置,可以通过config结构体设置DMA通道宽度、数据传输宽带、源地址目的地址等信息。 */
int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config);

/* 3. 获得传输描述符 */
通过device_prep_slave_sg() 或者device_prep_dma_cyclic() 或者    device_prep_dma_memcpy() 获取desc,再将回调函数指针穿给desc->callback

/* 4. 提交传输 */
调用dmaengine_submit((struct dma_async_tx_descriptor *)desc),将desc提交到DMA等待队列

/* 5. 启动传输 */
dmaengine_issue_pending调用会从第一个描述符开始进行传输。如果DMA 设备驱动有回调函数的话,会在传输完成后执行。

8. DMA framework

        在drivers/dma目录下,有一个dmaengine.c文件,该文件是DMA框架的核心,向下提供注册DMA host driver的接口(dma_async_device_register),向上提供操作DMA的接口(申请DMA通道、启动传输等等)。

原网站

版权声明
本文为[四季帆]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_41076734/article/details/124714012