dma_alloc_coherent 相关的知识网络上有很多,将相关的知识收集汇总以便于后续遇到问题方便查找。这一次对dma_alloc_coherent进行学习的原因是在使用rapidio驱动时申请大于64M的DMA内存时,遇到多次申请无法申请到内存的情况(加大预留DMA内存不能解决)。进而学习一下相关知识看是否有方法能解决相关问题。
遇到的问题DMA无法申请大于32M的内存,MAX_ORDER 这个宏设置的太小,这个宏限制了一次请求所能分配的最大物理页数。如果申请5MB内存,MAX_ORDER 需要不小于12。决方法是修改menuconfig的CONFIG_FORCE_MAX_ZONEORDER定义。
内存物理内存分配alloc_pages
内存管理
能够申请多大的DMA内存,和对应内存个数可以看/proc/buddyinfo。
buddyinfo说明
Linux 内核内存管理
Linux CMA内存使用
深入理解Linux高端内存
mmzone.h中
/* Free memory management - zoned buddy allocator. */
#ifndef CONFIG_FORCE_MAX_ZONEORDER
#define MAX_ORDER 11
#else
#define MAX_ORDER CONFIG_FORCE_MAX_ZONEORDER
#endif
#define MAX_ORDER_NR_PAGES (1 << (MAX_ORDER - 1))
DMA使用CMA的设置,不开启设置DMA将不能使用CMA内存。在dma_alloc_coherent 调用时使用或上__GFP_HIGHMEM参数可以避免使用Normal 空间。
对于dma framwork来说,当我们使能并且配置了CMA区域时会使用CMA进行内存分配,但是内核依然对于旧的实现方式进行了兼容,可以通过 CONFIG_HAVE_GENERIC_DMA_COHERENT 来进行配置。
对于generic dma coherent的实现方式,依然借用了dts中的reserved memory节点,只不过在其中会定义 no-map 属性,进而这块内存就从系统中剥离出来了,无法被伙伴系统所使用,但是可以在dma核心层通过remap的形式创建页表映射来使用它。
在“内存管理”的连接上有内存分配的原理如下说明:
分配器维护空闲页面所组成的块, 这里每一块都是2的方幂个页面, 方幂的指数称为阶.
阶是伙伴系统中一个非常重要的术语. 它描述了内存分配的数量单位. 内存块的长度是2^0,order , 其中order的范围从0到MAX_ORDER
zone->free_area[MAX_ORDER]数组中阶作为各个元素的索引, 用于指定对应链表中的连续内存区包含多少个页帧.
buddyinfo说明中有如下图:
ZONE的空间配置,但是对于ARM 可能没有ZONE DMA部分。ZONE_DMA,对于32位系统和64位系统表达的意义是不同的,ZONE_DMA32则只对64位系统有意义,对32位系统就等同于ZONE_DMA,没有单独存在的意义。
在使用dma_alloc_coherent申请一致性内存的时候,有时候如果DTS有大量预留内存被分频。第一次会申请64M大内存失败。如果再次运行程序申请又能正常申请,申请失败时报下面的错误。
vmap allocation for size XXXX failed: use vmalloc=XXX to increase size
查看ZONE区域发现有足够的内存空间,怀疑是系统的vmalloc 虚拟地址连续大小资源不足。该资源在系统启动的时候就存在了,在UBOOT阶段可以进行设置。
查看虚拟内存大小( cat /proc/meminfo)
$ cat /proc/meminfo
.....
MemTotal: 1061096 kB
MemFree: 987244 kB
MemAvailable: 969068 kB
Buffers: 0 kB
Cached: 45716 kB
SwapCached: 0 kB
Active: 1840 kB
Inactive: 24 kB
Active(anon): 1840 kB
Inactive(anon): 24 kB
Active(file): 0 kB
Inactive(file): 0 kB
Unevictable: 45644 kB
Mlocked: 0 kB
HighTotal: 828928 kB
HighFree: 779192 kB
LowTotal: 232168 kB
LowFree: 208052 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 0 kB
Writeback: 0 kB
AnonPages: 1800 kB
Mapped: 2752 kB
Shmem: 72 kB
Slab: 16740 kB
SReclaimable: 9436 kB
SUnreclaim: 7304 kB
KernelStack: 624 kB
PageTables: 144 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 530548 kB
Committed_AS: 3984 kB
VmallocTotal: 245760 kB
VmallocUsed: 0 kB
VmallocChunk: 0 kB
Percpu: 320 kB
CmaTotal: 262144 kB
CmaFree: 260280 kB
......
查看虚拟内存使用情况( cat /proc/meminfo)
$ cat /proc/vmallocinfo
.....
0x(ptrval)-0x(ptrval) 8298496 simplefb_probe+0x210/0x678 phys=0x70000000 ioremap //DTS 中的显示保留内存 dc_reserved
0x(ptrval)-0x(ptrval) 266240 __devm_ioremap+0x4c/0x98 phys=0xfd500000 ioremap
0x(ptrval)-0x(ptrval) 4198400 __devm_ioremap+0x4c/0x98 phys=0xf9000000 ioremap
0x(ptrval)-0x(ptrval) 274432 deflate_comp_init+0x20/0x9c pages=66 vmalloc
0x(ptrval)-0x(ptrval) 1052672 __devm_ioremap+0x4c/0x98 phys=0xf0300000 ioremap
0x(ptrval)-0x(ptrval) 1052672 __devm_ioremap+0x4c/0x98 phys=0xf0400000 ioremap
0x(ptrval)-0x(ptrval) 1052672 __devm_ioremap+0x4c/0x98 phys=0xf0000000 ioremap
......
在测试中发现使用的图像接口的接收缓存添加了no-map参数,有这个参数在会导致该段内存在启动的时候被系统使用ioremap将对应地址段占用,用ioremap会消耗虚拟地址范围导致没有虚拟地址分配给其他映射。
.....
mipi_buffer: mipi_buffer@0x3EE00000 {
compatible = "shared-dma-pool";
reg = <0x3EE00000 0x08000000>;
no-map;
};
camera_link_buffer: camera_link_buffer@0x46E00000 {
compatible = "shared-dma-pool";
reg = <0x46E00000 0x08000000>;
no-map;
};
dc_reserved: dc_mem@0x70000000 {
compatible = "shared-dma-pool";
reg = <0x70000000 0x2000000>;
no-map;
};
........
一点相关的说明
vmalloc.h:201:#define VMALLOC_TOTAL (VMALLOC_END - VMALLOC_START)
1.查看/proc/meminfo的vmalloc大小,在uboot启动参数中调大点。
setenv bootargs ‘mem=1G console=ttyAMA0,115200 mtdparts=hi_sfc:1M(boot),8M(kernel),20M(app),2M(nand),1M(param) slave_bhisilicon # 64M console=ttyAMA0,115200 vmalloc=500M’
2.或者修改KENERL 内部的,需要修改KENERL 的内容。
/kernel/arch/arm$ grep -nr "VMALLOC_END"
include/asm/pgtable.h:47:#define VMALLOC_END 0xff800000UL
include/asm/pgtable-nommu.h:87:#define VMALLOC_END 0xffffffffUL
kernel/module.c:55: return __vmalloc_node_range(size, 1, VMALLOC_START, VMALLOC_END,
mm/mmu.c:971: (md->virtual < VMALLOC_START || md->virtual >= VMALLOC_END)) {
mm/mmu.c:1132: (void *)(VMALLOC_END - (240 << 20) - VMALLOC_OFFSET);
mm/mmu.c:1149: if (vmalloc_reserve > VMALLOC_END - (PAGE_OFFSET + SZ_32M)) {
mm/mmu.c:1150: vmalloc_reserve = VMALLOC_END - (PAGE_OFFSET + SZ_32M);
mm/mmu.c:1155: vmalloc_min = (void *)(VMALLOC_END - vmalloc_reserve);
mm/init.c:552: MLM(VMALLOC_START, VMALLOC_END),
mm/dump.c:30: { VMALLOC_END, "vmalloc() End" },
mm/iomap.c:39: (unsigned long)addr < VMALLOC_END)
mm/pageattr.c:60: !in_range(start, size, VMALLOC_START, VMALLOC_END))
mm/ioremap.c:123: sizeof(pgd_t) * (pgd_index(VMALLOC_END) -
修改mmu.c的240 值为其他值能修改虚拟地址的大小,一般建议是1G->240,内存较大可以对应修改。
static void * __initdata vmalloc_min =
(void *)(VMALLOC_END - (240 << 20) - VMALLOC_OFFSET);
/*
* vmalloc=size forces the vmalloc area to be exactly 'size'
* bytes. This can be used to increase (or decrease) the vmalloc
* area - the default is 240m.
3.将DTS中预留内存中没有必要no-map的去掉no_map参数。