DMA内存申请--dma_alloc_coherent 及 寄存器与内存【转】
转自:https://blog.csdn.net/ic_soc_arm_robin/article/details/8203933
在项目驱动过程中会经常用到dma传输数据,而dma需要的内存有自己的特点,一般认为需要物理地址连续,并且内存是不可cache的,在linux内核中提供一个供dma所需内存的申请函数dma_alloc_coherent. 如下所述:
dma_alloc_coherent()
dma_alloc_coherent() -- 获取物理页,并将该物理页的总线地址保存于dma_handle,返回该物理页的虚拟地址
DMA映射建立了一个新的结构类型---------dma_addr_t来表示总线地址。dma_addr_t类型的变量对驱动程序是不透明的;唯一允许的操作是将它们传递给DMA支持例程以及设备本身。作为一个总线地址,如果CPU直接使用了dma_addr_t,将会导致发生不可预期的后果!
一致性DMA映射存在与驱动程序的生命周期中,它的缓冲区必须可同时被CPU和外围设备访问!因此一致性映射必须保存在一致性缓存中。建立和使用一致性映射的开销是很大的!
(以上红色文字摘录在LDD3)
void *
dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp)
{
void *ret;
if (!dev || *dev->dma_mask >= 0xffffffffUL)
gfp &= ~GFP_DMA;
ret = (void *)__get_free_pages(gfp, get_order(size));
//(1)
if (ret) {
memset(ret, 0, size);
*dma_handle = virt_to_bus(ret);
//(2)
}
return ret;
}
(1) 将size转换成order, 即2^order
(2) 将虚拟地址ret转换成总线地址
这个函数是一个平台相关的函数,以上是在x86平台的实现细节,从这里我们可以看到该函数返回值为linux 内核线性地址,所以对于驱动开发过程的mmap函数实现提供了便利。
但是在powerpc平台却不是这样,笔者就曾经遇到在将pci驱动从x86平台移植到powerpc平台时出现问题。
首先我们来先看一下两个平台对于dma内存的处理。
x86:
linux内存区域分为DMA区域,Normal内存区域与高端内存区域,高端内存区域为当物理内存高于768M时使用,一般DMA区域为16M,这段空间由操作系统预留。DMA区域与Normal区域全部使用线性映射,采用逻辑地址使用,高端内存使用内核虚拟地址。其中内核空间的分部为:
物理区--8M隔离--vmalloc区--8k隔离--4M的高端映射区--固定映射区--128k
powerpc:
本节采用freescale的mpc5121芯片为例,内核没有采用Normal内存区域,只使用ZONE_DMA和ZONE_HIMEM两种类型的空间,其中ZONE_DMA存放低端内存,
ZONE_HIMEN存放高端内存,整个内存不在采用逻辑地址这一概念。所以基于逻辑地址的操作没有可移植性。
下面看下具体的区别:
void * __dma_alloc_coherent(size_t size, dma_addr_t *handle, gfp_t gfp)
{
//物理空间页的申请
page = alloc_pages(gfp, order);
//对物理空间进行清零cache
{
unsigned long kaddr = (unsigned long)page_address(page);
memset(page_address(page), 0, size);
flush_dcache_range(kaddr, kaddr + size);
}
//申请虚拟空间
c = vm_region_alloc(&consistent_head, size, gfp &
~(__GFP_DMA | __GFP_HIGHMEM));
//实现虚拟地址与物理地址映射
if (c) {
unsigned long vaddr = c->vm_start;
pte_t *pte = consistent_pte + CONSISTENT_OFFSET(vaddr);
struct page *end = page + (1 << order);
split_page(page, order);
/*
* Set the \"dma handle\"
*/
*handle = page_to_bus(page);
do {
BUG_ON(!pte_none(*pte));
SetPageReserved(page);
set_pte_at(&init_mm, vaddr,
pte, mk_pte(page, pgprot_noncached(PAGE_KERNEL)));
page++;
pte++;
vaddr += PAGE_SIZE;
} while (size -= PAGE_SIZE);
// 返回值为内核虚拟地址。
return (void *)c->vm_start
1. I/O 端口和 I/O 内存
每个外设都是通过读写它的寄存器来控制. 大部分时间一个设备有几个寄存器, 并且在连续地址存取它们, 或者在内存地址空间或者在 I/O 地址空间.
在硬件级别上, 内存区和 I/O 区域没有概念上的区别: 它们都是通过在地址总线和控制总线上发出电信号来存取(即, 读写信号)[32]并且读自或者写到数据总线.
但是一些 CPU 制造商在他们的芯片上实现了一个单个地址空间, 有人认为外设不同于内存, 因此, 应该有一个分开的地址空间. 一些处理器(最有名的是 x86 家族)有分开的读和写电线给 I/O 端口和特殊的 CPU 指令来存取端口.
因为外设被建立来适合一个外设总线, 并且大部分流行的 I/O 总线成型在个人计算机上, 即便那些没有单独地址空间给 I/O 端口的处理器,
也必须在存取一些特殊设备时伪装读写端口, 常常利用外部的芯片组或者 CPU 核的额外电路. 后一个方法在用在嵌入式应用的小处理器中常见.
由于同样的理由, Linux 在所有它运行的计算机平台上实现了 I/O 端口的概念, 甚至在那些 CPU 实现一个单个地址空间的平台上. 端口存取的实现有时依赖特殊的主机制造和型号( 因为不同的型号使用不同的芯片组来映射总线传送到内存地址空间).
即便外设总线有一个单独的地址空间给 I/O 端口, 不是所有的设备映射它们的寄存器到 I/O 端口. 虽然对于 ISA 外设板使用 I/O
端口是普遍的, 大部分 PCI 设备映射寄存器到一个内存地址区. 这种 I/O 内存方法通常是首选的, 因为它不需要使用特殊目的处理器指令;
CPU 核存取内存更加有效, 并且编译器当存取内存时有更多自由在寄存器分配和寻址模式的选择上.
1.1. I/O 寄存器和常规内存
不管硬件寄存器和内存之间的强相似性, 存取 I/O 寄存器的程序员必须小心避免被 CPU(或者编译器)优化所戏弄, 它可能修改希望的 I/O 行为.
I/O 寄存器和 RAM 的主要不同是 I/O 操作有边际效果, 而内存操作没有: 一个内存写的唯一效果是存储一个值到一个位置,
并且一个内存读返回最近写到那里的值. 因为内存存取速度对 CPU 性能是至关重要的, 这种无边际效果的情况已被多种方式优化: 值被缓存, 并且
读/写指令被重编排.
编译器能够缓存数据值到 CPU 寄存器而不写到内存, 并且即便它存储它们, 读和写操作都能够在缓冲内存中进行而不接触物理 RAM.
重编排也可能在编译器级别和在硬件级别都发生: 常常一个指令序列能够执行得更快, 如果它以不同于在程序文本中出现的顺序来执行, 例如, 为避免在
RISC 流水线中的互锁. 在CISC 处理器, 要花费相当数量时间的操作能够和其他的并发执行, 更快的.
当应用于传统内存时(至少在单处理器系统)这些优化是透明和有益的, 但是它们可能对正确的 I/O 操作是致命的,
因为它们干扰了那些"边际效果", 这是主要的原因为什么一个驱动存取 I/O 寄存器. 处理器无法预见这种情形,
一些其他的操作(在一个独立处理器上运行, 或者发生在一个 I/O 控制器的事情)依赖内存存取的顺序. 编译器或者 CPU
可能只尽力胜过你并且重编排你请求的操作; 结果可能是奇怪的错误而非常难于调试. 因此,
一个驱动必须确保没有进行缓冲并且在存取寄存器时没有发生读或写的重编排.
硬件缓冲的问题是最易面对的:底层的硬件已经配置(或者自动地或者通过 Linux 初始化代码)成禁止任何硬件缓冲, 当存取 I/O 区时(不管它们是内存还是端口区域).
对编译器优化和硬件重编排的解决方法是安放一个内存屏障在必须以一个特殊顺序对硬件(或者另一个处理器)可见的操作之间. Linux 提供 4 个宏来应对可能的排序需要:
- #include <linux/kernel.h>
- void barrier(void)
-
这个函数告知编译器插入一个内存屏障但是对硬件没有影响. 编译的代码将所有的当前改变的并且驻留在 CPU 寄存器的值存储到内存, 并且后来重新读取它们当需要时. 对屏障的调用阻止编译器跨越屏障的优化, 而留给硬件自由做它的重编排.
- #include <asm/system.h>
- void rmb(void);
- void read_barrier_depends(void);
- void wmb(void);
- void mb(void);
-
这些函数插入硬件内存屏障在编译的指令流中; 它们的实际实例是平台相关的. 一个 rmb ( read memory barrier) 保证任何出现于屏障前的读在执行任何后续读之前完成. wmb 保证写操作中的顺序, 并且 mb 指令都保证. 每个这些指令是一个屏障的超集.
read_barrier_depends 是读屏障的一个特殊的, 弱些的形式. 而 rmb 阻止所有跨越屏障的读的重编排,
read_barrier_depends 只阻止依赖来自其他读的数据的读的重编排. 区别是微小的, 并且它不在所有体系中存在.
除非你确切地理解做什么, 并且你有理由相信, 一个完整的读屏障确实是一个过度地性能开销, 你可能应当坚持使用 rmb.- #define isb() __asm__ __volatile__ ("isb" : : : "memory")
- #define dsb() __asm__ __volatile__ ("dsb" : : : "memory")
- #define dmb() __asm__ __volatile__ ("dsb" : : : "memory")
- #define mb() do { dsb(); outer_sync(); } while (0)
- #define rmb() dsb()
- #define wmb() mb()
- #define smp_mb() dmb()
- #define smp_rmb() dmb()
- #define smp_wmb() dmb()
表4.27 隔离指令
指令名
功能描述
DMB
数据存储器隔离。DMB 指令保证: 仅当所有在它前面的存储器访问操作
都执行完毕后,才提交(commit)在它后面的存储器访问操作。
DSB
数据同步隔离。比 DMB 严格: 仅当所有在它前面的存储器访问操作
都执行完毕后,才执行在它后面的指令(亦即任何指令都要等待存储器访 问操作——译者注)
ISB
指令同步隔离。最严格:它会清洗流水线,以保证所有它前面的指令都执
行完毕之后,才执行它后面的指令。
- void smp_rmb(void);
- void smp_read_barrier_depends(void);
- void smp_wmb(void);
- void smp_mb(void);
-
屏障的这些版本仅当内核为 SMP 系统编译时插入硬件屏障; 否则, 它们都扩展为一个简单的屏障调用.
在一个设备驱动中一个典型的内存屏障的用法可能有这样的形式:
writel(dev->registers.addr, io_destination_address);
writel(dev->registers.size, io_size);
writel(dev->registers.operation, DEV_READ);
wmb();
writel(dev->registers.control, DEV_GO);
在这种情况, 是重要的, 确保所有的控制一个特殊操作的设备寄存器在告诉它开始前已被正确设置. 内存屏障强制写以需要的顺序完成.
因为内存屏障影响性能, 它们应当只用在确实需要它们的地方. 屏障的不同类型也有不同的性能特性, 因此值得使用最特定的可能类型. 例如, 在 x86 体系上, wmb() 目前什么都不做, 因为写到处理器外不被重编排. 但是, 读被重编排, 因此 mb() 被 wmb() 慢.
值得注意大部分的其他的处理同步的内核原语, 例如自旋锁和原子的 _t 操作, 如同内存屏障一样是函数. 还值得注意的是一些外设总线(例如 PCI 总线)有它们自己的缓冲问题; 我们在以后章节遇到时讨论它们.
一些体系允许一个赋值和一个内存屏障的有效组合. 内核提供了几个宏来完成这个组合; 在缺省情况下, 它们如下定义:
#define set_mb(var, value) do {var = value; mb();} while 0
#define set_wmb(var, value) do {var = value; wmb();} while 0
#define set_rmb(var, value) do {var = value; rmb();} while 0
在合适的地方, <asm/system.h> 定义这些宏来使用体系特定的指令来很快完成任务. 注意 set_rmb 只在少量体系上定义. (一个 do...while 结构的使用是一个标准 C 用语, 来使被扩展的宏作为一个正常的 C 语句可在所有上下文中工作).
[32] 不是所有的计算机平台使用一个读和一个写信号; 有些有不同的方法来寻址外部电路. 这个不同在软件层次是无关的, 但是, 我们将假设全部有读和写来简化讨论.
DMA内存申请--dma_alloc_coherent 及 寄存器与内存【转】的更多相关文章
- 转:内核中的内存申请:kmalloc、vmalloc、kzalloc、kcalloc、get_free_pages
在内核模块中申请分配内存需要使用内核中的专用API:kmalloc.vmalloc.kzalloc.kcalloc.get_free_pages;当然,设备驱动程序也不例外;对于提供了MMU功能的处理 ...
- 内核中的内存申请:kmalloc、vmalloc、kzalloc、get_free_pages 之间的区别
kmalloc vmalloc kzalloc get_free_page()是内核空间申请内存空间函数 malloc是用户空间申请内存函数 一 ,kmalloc() 与 kfree() ...
- 内核中的内存申请:kmalloc、vmalloc、kzalloc、kcalloc、get_free_pages【转】
转自:http://www.cnblogs.com/yfz0/p/5829443.html 在内核模块中申请分配内存需要使用内核中的专用API:kmalloc.vmalloc.kzalloc.kcal ...
- 【转】内核中的内存申请:kmalloc、vmalloc、kzalloc、kcalloc、get_free_pages
转自:https://www.cnblogs.com/yfz0/p/5829443.html 在内核模块中申请分配内存需要使用内核中的专用API:kmalloc.vmalloc.kzalloc.kca ...
- CE 内存申请
char ch_ReadByte='H'; char *ptr_OneLineData; unsigned ); if ((ptr_OneLineData = (char *)malloc(bufsi ...
- Unity内存申请和释放
转自:http://www.jianshu.com/p/b37ee8cea04c 1.资源类型 GameObject, Transform, Mesh, Texture, Material, Shad ...
- JVM系列二:GC策略&内存申请、对象衰老
JVM里的GC(Garbage Collection)的算法有很多种,如标记清除收集器,压缩收集器,分代收集器等等,详见HotSpot VM GC 的种类 现在比较常用的是分代收集(generatio ...
- php内存申请和销毁
内存申请 ZendMM使用自身heap层申请内存追踪结果: ZEND_ASSIGN_SPEC_CV_CONST_HANDLER (......) -> ALLOC_ZVAL(......) -& ...
- Linux驱动技术(一) _内存申请
先上基础,下图是Linux的内存映射模型,其中体现了Linux内存映射的几个特点: 每一个进程都有自己的进程空间,进程空间的0-3G是用户空间,3G-4G是内核空间 每个进程的用户空间不在同一个物理内 ...
随机推荐
- HGOI 20180224 题解
/* The Most Important Things: ljc chat with fyh on QQTa说期末考Ta数学74分感觉不好但是我觉得fyh是地表最强的鸭~~(of course en ...
- 洛谷 P1407 [国家集训队]稳定婚姻 解题报告
P1407 [国家集训队]稳定婚姻 题目描述 我国的离婚率连续7年上升,今年的头两季,平均每天有近5000对夫妇离婚,大城市的离婚率上升最快,有研究婚姻问题的专家认为,是与简化离婚手续有关. 25岁的 ...
- hdu3072 Intelligence System (最小树形图?)
题意:给一个有向图,问要从0号点能到达所有点所需要经过路径的最小权值和是多少,然而,若两点强联通,则这两点互相到达不需要花费.保证0号点能到达所有点 tarjan缩点以后直接取每个点入边中花费最小的即 ...
- N皇后问题(DFS)
题目:在N*N的国际象棋棋盘上放置N个皇后彼此不受攻击(即在棋盘的任一行,任一列和任意对角线上不能放置2个皇后),求解所有摆放方案的总数. 样例输入: 1 8 样例输出: 1 92 解题思路:由于皇后 ...
- 洛谷P2668 斗地主
好,终于搞完了这一道毒瘤题...... 先想到搜索,然后想到状压,发现数据组数很多,又是随机,还是决定用搜索. 先搜出的多的,于是顺序是三个顺子,然后按照多到少搜带牌,最后是不带牌. 大体思路很简单, ...
- 记录一次react相关总结
背景说明:元旦接到一个管理后台的项目,是一个关于自定义专题的项目.通过后台的配置自定义专题,前端根据专题模块进行渲染数据.由于管理后台是react做的前后分离,对于一个后端的我来说写写js也算是浅尝一 ...
- 3分钟学会sessionStorage用法
前言: 因最近移动端开发过程中遇到一个运营提出的所谓技术难点需求,对于原生APP来说轻而易举,毕竟自己的APP用户操作指哪打哪,但是H5该怎么做?H5就实现不了么?对于一个爱研究攻克这些前端棘手问题的 ...
- php 防止sql注入的简单方法
您可以使用PHP的功能,如函数stripslashes()和addslashes(),mysql_real_escape_string()等,使安全的SQL查询. 以下是安全的SQL语句,使用PHP ...
- 20145215《网络对抗》Exp6 信息搜集与漏洞扫描
20145215<网络对抗>Exp6 信息搜集与漏洞扫描 基础问题回答 哪些组织负责DNS,IP的管理? 全球根服务器均由美国政府授权的ICANN统一管理,负责全球的域名根服务器.DNS和 ...
- nodejs实现新闻爬虫
作为费德勒的铁杆粉丝,每天早上都会在新浪体育里面的网球频道浏览费德勒新闻.由于只关注费德勒的新闻,所以每次都要在网页中大量的新闻中筛选相关信息,感觉效率好低,所以用node写了一个简单的爬虫程序通过每 ...