Linux DMA Engine framework(1)_概述
作者:wowo 发布于:2017-3-30 22:01 分类:Linux内核分析
1. 前言
前面文章介绍“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有关的技术细节。
2. 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驱动的时候,自然会知道怎么做)。
3. 总结
本文简单的介绍了DMA传输有关的概念、术语,接下来将会通过下面两篇文章,介绍Linux DMA engine有关的实现细节:
Linux DMA Engine framework(2)_provider
Linux DMA Engine framework(3)_consumer
4. 参考文档
[1] Documentation/dmaengine/provider.txt
原创文章,转发请注明出处。蜗窝科技,www.wowotech.net。
标签: Linux Kernel 内核 framework dma engine

评论:
2017-09-14 09:49
在DMA方面我一直有个疑问,DMA请求总线后,CPU把总线控制权交给了DMA,那么CPU不是就没法从内存读取指令和数据了吗?那么,CPU不就处于干瞪眼的状态吗?这样的话,也没法提高CPU的效率啊,还不然直接让CPU去干DMA的活呢。我的这个想法的bug,在哪里呢?
2017-09-14 10:45
你的疑问是对的,资源是有限的,肯定有争抢和效率的问题(不仅仅是memory,在memory之前还存在对总线的争抢)。但也不用过于悲观,因为(我们从CPU的视角往下说):
1)CPU不是一直在取指和取数。指令执行的过程包取指、译码和执行,译码肯定不需要访问memory,执行访问memory的概率也不会超过50%。
2)复杂SoC通常有很多级的指令cache和数据cache,在顺序执行的情况下,又可以大大减少CPU访问memory的可能性,降低和DMA冲突、争抢的概率。
3)复杂SoC的总线通常具有仲裁能力,可以配置CPU、DMA等访问总线的优先级,这很大程度上会影响CPU和DMA对memory的访问,如果不想CPU被影响,可以调高它的优先级,反正DMA传输可以慢慢来(见缝插针,这也是DMA设计的初衷)。
4)关于CPU和DMA对memory资源的争抢,也可以通过DMA burst size进行调整。
5)很多时候,系统中的memory(例如DDR),是有并发访问的能力的,可以想象成有多个可并行工作的memory,因此竞争的概率又可以降低了。
6)最后再强调:DMA的性能和CPU的性能是不可调和的,需要根据实际的应用场景小心的平衡。
2017-09-07 20:33
2017-08-31 14:47
假设usb iso传输的数据包大小是256(很有可能比这还大)
如果burst size是256,传输的行为是这样的:
1. dma从USB读取256B数据
2. dma将这256B的数据写入到ram
...
如果burst size是32,传输的行为是这样的:
1. dma从USB读取32B的数据
2. dma将32B的数据写入memory
3. dma再从USB读取32B的数据
4. dma再将32B的数据写入memory
...
如此反复8次。
那么问题来了,那种方法好呢?
暂且不管dma和usb设备之间的事情,只看dma和memory之间的交互,是一次写入256B写1次好呢,还是一次写入32B写8次好呢?
前者,dma抢到总线后,一直把数据写完才放。后者,分批次写入,意味着中间可以把总线放掉给其他人(或者其他dma)使用。
是不是意味着:前者对自己好,后者对其他人好?那么你想要哪种效果呢?
最后,虽然理论上是这样的,实际上怎么样呢?对系统性能影响大吗?不得而知,你可以试试。试验结果才是最有说服力的。
功能
最新评论
- wangjing
写得太好了 - wangjing
写得太好了! - DRAM
圖面都沒辦法顯示出來好像掛點了。 - Simbr
bus至少是不是还有个subsystem? - troy
@testtest:只要ldrex-modify-strex... - gh
Linux 内核在 sparse 内存模型基础上实现了vme...
文章分类
随机文章
文章存档
- 2025年4月(5)
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
2023-09-28 16:02
我这里遇到一个dma 直接映射物理内存的问题,是用cma的机制,我发的时间太长了,都还没有 解决,能否帮忙提供思路,非常感谢!!!
驱动qca-7850调试的结果和我写的关于A函数的内核测试模块的结果一样,我就用我的测试程序描述一下问题。
问题1
linux 内核中 dma_addr是u64类型,size是size_t类型, min_not_zero(dev->coherent_dma_mask, dev->bus_dma_limit)的结果是0xffffffff;
1.1 当dma_addr=0x730bb000(alloc_pages_node分配), size=0x1000 ,关系表达式 dma_addr + size - 1 <= min_not_zero(dev->coherent_dma_mask, dev->bus_dma_limit),
最后结果是1.
1.2 当dma_addr=0x600000(dma_alloc_contiguous) size=0x6c0000,关系表达式 dma_addr + size - 1 <= min_not_zero(dev->coherent_dma_mask, dev->bus_dma_limit),
最后结果为什么是0.
问题2
怎么用A函数通过cma机制申请直接映射物理内存?
背景描述如下:
linux kernel 5.16.0-rc8 ------自己编译的
ubuntu 22.04
我用 dma_alloc_coherent函数分配DMA的相关联内存,结果分配失败,发现__dma_direct_alloc_pages函数调用dma_alloc_contiguous后能得到虚拟地址和DMA物理地址,
用dma_coherent_ok检查后,发现关系表达式为假。
return dma_addr + size - 1 <=
min_not_zero(dev->coherent_dma_mask, dev->bus_dma_limit);
,最后释放dma_alloc_contiguous申请的cma的地址空间,最后调用普通的分配内存机制alloc_pages_node,由于此时申请的
空间(0x6f0000)比较大,就申请失败了。如果申请空间比较小,就能通过alloc_pages_node分配成功。
相应的内核编译配置如下:
1.Build the Linux kernel
make menuconfig
CONFIG_CFG80211_INTERNAL_REGDB=y
CONFIG_CFG80211=y
CONFIG_NL80211_TESTMODE=y
CONFIG_FRAME_WARN=2048
CONFIG_CMA_SIZE_MBYTES=512
CONFIG_IRQ_REMAP=y
CONFIG_DMA_CMA=y
CONFIG_CMA_SIZE_SEL_MBYTES=y
CONFIG_CMA_ALIGNMENT=8