宋宝华:关于DMA ZONE和dma_alloc_coherent若干误解的澄清

本文已首先在Linuxer公众号(ID: LinuxDev)发表,现转回我的blog也发表。转载请注明出处。

1.DMA ZONE的大小是16MB?

这个答案在32位X86计算机的条件下是成立的,但是在其他的绝大多数情况下都不成立。

首先我们要理解DMA ZONE产生的历史原因是什么。DMA可以直接在内存和外设之间进行数据搬移,对于内存的存取来讲,它和CPU一样,是一个访问master,可以直接访问内存。

DMA ZONE产生的本质原因是:不一定所有的DMA都可以访问到所有的内存,这本质上是硬件的设计限制。

在32位X86计算机的条件下,ISA实际只可以访问16MB以下的内存。那么ISA上面假设有个网卡,要DMA,超过16MB以上的内存,它根本就访问不到。所以Linux内核干脆简单一点,把16MB砍一刀,这一刀以下的内存单独管理。如果ISA的驱动要申请DMA buffer,你带一个GFP_DMA标记来表明你想从这个区域申请,我保证申请的内存你是可以访问的。

DMA ZONE的大小,以及DMA ZONE要不要存在,都取决于你实际的硬件是什么。比如我在CSR工作的时候,CSR的primaII芯片,尽管除SD MMC控制器以外的所有的DMA都可以访问整个4GB内存,但MMC控制器的DMA只能访问256MB,我们就把primaII对应Linux的DMA ZONE设为了256MB,详见内核:arch/arm/mach-prima2/common.c

#ifdef CONFIG_ARCH_PRIMA2

static const char *const prima2_dt_match[] __initconst = {

"sirf,prima2",

NULL

};

DT_MACHINE_START(PRIMA2_DT, "Generic PRIMA2 (Flattened Device Tree)")

/* Maintainer: Barry Song <[email protected]> */

.l2c_aux_val    = 0,

.l2c_aux_mask   = ~0,

.dma_zone_size  = SZ_256M,

.init_late      = sirfsoc_init_late,

.dt_compat      = prima2_dt_match,

MACHINE_END

#endif

不过CSR这个公司由于早前已经被Q记收购,已经不再存在,一起幻灭的,还有当年挂在汽车前窗上的导航仪。这不禁让我想起我们当年在ADI arch/blackfin里面写的代码,也渐渐快几乎没有人用了一样。

一代人的芳华已逝,面目全非,重逢虽然谈笑如故,可不难看出岁月给每个人带来的改变。原谅我不愿让你们看到我们老去的样子,就让代码,留住我们芬芳的年华吧........

下面我们架空历史,假设有一个如下的芯片,里面有5个DMA,A、B、C都可以访问所有内存,D只能访问32MB,而E只能访问64MB,你觉得Linux的设计者会把DMA ZONE设置为多大?当然是32MB,因为如果设置为64MB,D从DMA ZONE申请的内存就可能位于32MB-64MB之间,申请了它也访问不了。

由于现如今绝大多少的SoC都很牛逼,似乎DMA都没有什么缺陷了,根本就不太可能给我们机会指定DMA ZONE大小装逼了,那个这个ZONE就不太需要存在了。反正任何DMA在任何地方申请的内存,这个DMA都可以存取到。

2.DMA ZONE的内存只能做DMA吗?

DMA ZONE的内存做什么都可以。DMA ZONE的作用是让有缺陷的DMA对应的外设驱动申请DMA buffer的时候从这个区域申请而已,但是它不是专有的。其他所有人的内存(包括应用程序和内核)也可以来自这个区域。

3.dma_alloc_coherent()申请的内存来自DMA ZONE?

dma_alloc_coherent()申请的内存来自于哪里,不是因为它的名字前面带了个dma_就来自DMA ZONE的,本质上取决于对应的DMA硬件是谁。看代码:

[cpp] view plain copy

  1. static void *__dma_alloc(struct device *dev, size_t size, dma_addr_t *handle,
  2. gfp_t gfp, pgprot_t prot, bool is_coherent, const void *caller)
  3. {
  4. u64 mask = get_coherent_dma_mask(dev);
  5. if (mask < 0xffffffffULL)
  6. gfp |= GFP_DMA;
  7. }

对于primaII而言,绝大多少的外设的dma_coherent_mask都设置为0XffffffffULL(4GB内存全覆盖),但是SD那个则设置为256MB-1对应的数字。这样当primaII的SD驱动调用dma_alloc_coherent()的时候,GFP_DMA标记被设置,以指挥内核从DMA ZONE申请内存。但是,其他的外设,mask覆盖了整个4GB,调用dma_alloc_coherent()获得的内存就不需要一定是来自DMA ZONE。

4.dma_alloc_coherent()申请的内存是非cache的吗?

要解答这个问题,首先要理解什么叫cache coherent。还是继续看这个DMA的图,我们假设MEM里面有一块红色的区域,并且CPU读过它,于是红色区域也进CACHE:


但是,假设现在DMA把外设的一个白色搬移到了内存原本红色的位置:

这个时候,内存虽然白了,CPU读到的却还是红色,因为CACHE命中了,这就出现了cache的不coherent。
当然,如果是CPU写数据到内存,它也只是先写进cache(不一定进了内存),这个时候如果做一个内存到外设的DMA操作,外设可能就得到错误的内存里面的老数据。
所以cache coherent的最简单方法,自然是让CPU访问DMA buffer的时候也不带cache。事实上,缺省情况下,dma_alloc_coherent()申请的内存缺省是进行uncache配置的。
但是,由于现代SoC特别强,这样有一些SoC里面可以用硬件做CPU和外设的cache coherence,如图中的cache coherent interconnect:

这些SoC的厂商就可以把内核的通用实现overwrite掉,变成dma_alloc_coherent()申请的内存也是可以带cache的。这部分还是让大牛Arnd Bergmann童鞋来解释:

来自:https://www.spinics.net/lists/arm-kernel/msg322447.html

Arnd Bergmann:

dma_alloc_coherent() is a wrapper around a device-specific allocator,

based on the dma_map_ops implementation. The default allocator

from arm_dma_ops gives you uncached, buffered memory. It is expected

that the driver uses a barrier (which is implied by readl/writel

but not __raw_readl/__raw_writel or readl_relaxed/writel_relaxed)

to ensure the write buffers are flushed.

If the machine sets arm_coherent_dma_ops rather than arm_dma_ops,

the memory will be cacheable, as it's assumed that the hardware

is set up for cache-coherent DMAs.

当我grep内核源代码的时候,我发现部分SoC确实是这样实现的:

[email protected]:~/develop/linux/arch/arm$ git grep arm_coherent_dma_ops

include/asm/dma-mapping.h:extern struct dma_map_ops arm_coherent_dma_ops;

mach-highbank/highbank.c:               set_dma_ops(dev, &arm_coherent_dma_ops);

mach-mvebu/coherency.c: set_dma_ops(dev, &arm_coherent_dma_ops);

5.dma_alloc_coherent()申请的内存一定是物理连续的吗?

绝大多数的SoC目前都支持和使用CMA技术,并且多数情况下,DMA coherent APIs以CMA区域为申请的后端,这个时候,dma alloc coherent本质上用__alloc_from_contiguous()从CMA区域获取内存,申请出来的内存显然是物理连续的。这一点,在设备树dts里面就可以轻松配置,要么配置一个自己特定的cma区域,要么从“linux,cma-default”指定的缺省的CMA池子里面取内存:

[plain] view plain copy

  1. reserved-memory {
  2. #address-cells = <1>;
  3. #size-cells = <1>;
  4. ranges;
  5. /* global autoconfigured region for contiguous allocations */
  6. linux,cma {
  7. compatible = "shared-dma-pool";
  8. reusable;
  9. size = <0x4000000>;
  10. alignment = <0x2000>;
  11. linux,cma-default;
  12. };
  13. display_reserved: [email protected] {
  14. reg = <0x78000000 0x800000>;
  15. };
  16. multimedia_reserved: [email protected] {
  17. compatible = "acme,multimedia-memory";
  18. reg = <0x77000000 0x4000000>;
  19. };
  20. };

但是,如果IOMMU存在(ARM里面叫SMMU)的话,DMA完全可以访问非连续的内存,并且把物理上不连续的内存,用IOMMU进行重新映射为I/O virtual address (IOVA):

所以dma_alloc_coherent()这个API只是一个前端的界面,它的内存究竟从哪里来,究竟要不要连续,带不带cache,都完全是因人而异的。

最后总结一句,千万不要被教科书和各种网上的资料懵逼了双眼,你一定要真正自己探索和搞清楚事情的本源。

今天看了《芳华》这部电影,感慨良多,遂作此文。

原文地址:http://blog.51cto.com/21cnbao/2063961

时间: 2024-10-10 02:34:46

宋宝华:关于DMA ZONE和dma_alloc_coherent若干误解的澄清的相关文章

宋宝华:Docker 最初的2小时(Docker从入门到入门)

最初的2小时,你会爱上Docker,对原理和使用流程有个最基本的理解,避免满世界无头苍蝇式找资料.本人反对暴风骤雨式多管齐下狂轰滥炸的学习方式,提倡迭代学习法,就是先知道怎么玩,有个感性认识,再深入学习高级用法,深层原理,一轮轮迭代.坚决反对一上来就搞几百页厚的东西把人脑子弄乱. Docker是什么? KVM, Virtualbox, Vmware是虚拟出机器,让每个实例看到一个单独的机器:而Docker是虚拟出操作系统,实现应用之间的隔离,让各个应用觉得自己有一个自己的操作系统,而且彼此之间隔

宋宝华- Docker 背后的故事之名称空间(1)

名称空间是在OS之上实现容器与主机隔离,以及容器之间互相隔离的Linux内核核心技术.根据<Docker 最初的2小时(Docker从入门到入门)>一文,名称空间本质上就是在不同的工作组里面封官许愿,让大家在各自的部门里面都是manager,而且彼此不冲突.本文接下来从细节做一些讨论. 由于本文敲的命令既有可能位于主机,又有可能位于新的名称空间(模拟容器),为了避免搞乱你的脑子,下面主机命令一概采用本颜色,而模拟容器类的命令一概采用本颜色.色盲读者,敬请谅解. 名称空间是什么? 名称空间(Na

宋宝华: CPU是如何访问到内存的?-MMU基本原理

由于很多童鞋大学的时候学<微机原理>都是打酱油,当老师苍蝇在讲台上发噪音,导致MMU这些基本知识都没有搞清楚,所以对计算机的认识一塌糊涂,Linux也无法学通.然后我经常被问到吐血,我觉得我不得不写点什么东西,让这些打酱油的童鞋,把基本的马步扎稳,当然这不是为了别人或者奉献,纯粹是为了避免无数次被问到,然后心中吐血,迟早有一天吐血而亡.为了能够活地久一点,特作此文. 对于一个有MMU的CPU而言,MMU开启后,CPU是这样寻址的:CPU任何时候,一切时候,发出的地址都是虚拟地址,这个虚拟地址发

伙伴系统之伙伴系统概述--Linux内存管理(十四)

日期 内核版本 架构 作者 GitHub CSDN 2016-09-02 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理 1 前景回顾 1.1 Linux内存管理的层次结构 Linux把物理内存划分为三个层次来管理 层次 描述 存储节点(Node) CPU被划分为多个节点(node), 内存则被分簇, 每个CPU对应一个本地物理内存, 即一个CPU-node对应一个内存簇bank,即每个内存簇被认为是一个节点 管理区(Zone)

伙伴系统之伙伴系统概述--Linux内存管理(十五)

在内核初始化完成之后, 内存管理的责任就由伙伴系统来承担. 伙伴系统基于一种相对简单然而令人吃惊的强大算法. Linux内核使用二进制伙伴算法来管理和分配物理内存页面, 该算法由Knowlton设计, 后来Knuth又进行了更深刻的描述. 伙伴系统是一个结合了2的方幂个分配器和空闲缓冲区合并计技术的内存分配方案, 其基本思想很简单. 内存被分成含有很多页面的大块, 每一块都是2个页面大小的方幂. 如果找不到想要的块, 一个大块会被分成两部分, 这两部分彼此就成为伙伴. 其中一半被用来分配, 而另

关于嵌入式如何学习(看了不后悔,给学技术的同行一条光明的路)

关于嵌入式如何学习,我相信有很多大牛回答得很专业,最近在知乎上看到一网名为----李brooks,~的网友对此进行了总结,我个人觉得非常好,还有其他两位网友li crifan和Tony Ho,毕竟我工作以来也还有好多东西没有接触,就有他说的那些中的部分内容,我们来看看他们说了什么内容: 有一位大学生四年级的网友提出这样的问题: 本人大四学生,专业为电气类的,有C语言,单片机,模电,数电的基础,一直想从事嵌入式方面的工作(感兴趣),但是以目前的水平,暂时还不能找到这方面的工作,所以一直在纠结是先找

Linux驱动platform

platform device<==> platform bus <==> platform driver 转自:platform设备驱动全透析 宋宝华 http://blog.csdn.net/21cnbao/article/details/5615421 1.1 platform总线.设备与驱动 在Linux 2.6的设备驱动模型中,关心总线.设备和驱动这3个实体,总线将设备和驱动绑定.在系统每注册一个设备的时候,会寻找与之匹配的驱动:相反的,在系统每注册一个驱动的时候,会寻找

platform设备驱动全透析

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://21cnbao.blog.51cto.com/109393/337609 1.1 platform总线.设备与驱动 在Linux 2.6的设备驱动模型中,关心总线.设备和驱动这3个实体,总线将设备和驱动绑定.在系统每注册一个设备的时候,会寻找与之匹配的驱动:相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成. 一个现实的Linux设备和驱动通常都需要挂接在

C语言嵌入式系统编程修炼

C语言嵌入式系统编程修炼 ?? 2008-08-19 作者:宋宝华 来源:天极网 ?? C语言嵌入式系统编程修炼之背景篇 本文的讨论主要围绕以通用处理器为中心的协议处理模块进行,因为它更多地牵涉到具体的C语言编程技巧 不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求其编程语言具备较强的硬件直接操作能力.无疑,汇编语言具备这样的特质.但是,归因于汇编语言开发过程的复杂性,它并不是嵌入式系统开发的一般选择.而与之相比,C语言--一种"高级的低级"语言,则成为嵌入式系