Dynamic DMA mapping
dma_alloc_coherent memory with mmap
CMA模块学习笔记
A deep dive into CMA
Linux内核最新的连续内存分配器(CMA)——避免预留大块内存
CMA 详细分析
CMA连续物理内存用户空间映射—(一)
CMA连续物理内存用户空间映射—(二)
linux cma内存管理
Linux-3.14.12内存管理笔记【连续内存分配器(CMA)】
LINUX CMA 详细分析
Linux内存管理:CMA
dts中memreserve和reserved-memory的区别
内存初始化代码分析(一):identity mapping和kernel image mapping
内存初始化代码分析(二):内存布局
内存初始化代码分析(三):创建系统内存地址映射
meminfo与vmallocinfo实例
Cortex-A8处理器memcpy的优化方案
ARM64-memcpy.S 汇编源码分析
/dev/mem可没那么简单
DPDK中的memcpy性能优化及思考
开机预留的方法有下面两种,
参考内核文档Documentation/DMA-API.txt和Documentation/DMA-API-HOWTO.txt。这个函数分配的内存从哪儿来,有书上说的是__alloc_pages,实现原理类似于__get_free_pages,受限于内核,所以最大分配4MB,但是现在内核都支持CMA机制,然后dma_alloc_coherent从CMA分配内存,参考CMA 详细分析中的图片,可以看到dma_alloc_coherent有两个分配途径。
这样最大应该就是CMA区的最大值,dma_alloc_coherent分配的内存不带cache。dma_alloc_coherent分配超过4MB空间内存失败,需要确保系统有足够的DMA内存可用,看宏CONSISTENT_DMA_SIZE 的值是否大于5MB,这个值必须是2M的倍数。在一些版本的内核中,这个宏是 DEFAULT_CONSISTENT_DMA_SIZE。如果上面没问题,但是仍然申请失败,可能是 MAX_ORDER 这个宏设置的太小,这个宏限制了一次请求所能分配的最大物理页数。如果申请5MB内存,MAX_ORDER 需要不小于12。
DMA POOL分配小型一致性DMA映射。调用dma_alloc_coherent函数获得的映射,最小大小为单个页。如果需要的DMA区域还小,就可以用DMA池。应用时,首先创建DMA池,size是分配的缓冲区的大小,align是硬件对齐字节数,allocation表示内存边界不能超越allocation。
struct dma_pool *dma_pool_create(const char *name, struct device *dev, size_t size, size_t align, size_t allocation);
void dma_pool_destroy(struct dma_pool *pool);
分配和释放,
void * dma_pool_alloc(sturct dma_pool *pool, int mem_flags, dma_addr_t *handle);
void dma_pool_free(struct dma_pool *pool, void *addr, dma_addr_t addr);
在销毁之前必须向DMA池返回所有分配的内存。
最大4MB,带cache。
博客增大dma的分配,记录了分配大容量CMA的调试过程,总结一下就是实现1G的物理内存留700MB给硬件。第一,当CMA=500MB时,为了加载CMA成功,将内核,设备树,initrd/ramdisk放到前240MB,离散防止导致没有600MB连续空间。第二,通过开机预留256MB内存,为了ioremap 几百MB的内存,将user/kernel内存分配改为1G/3G,否则没有足够空间remap。第三,当CMA=700MB时,必须设置user/kernel内存分配改为1G/3G,否则无法加载CMA,因为1GB空间预留240MB给vmalloc(Linux内核版本从3.2到3.3,默认的vmalloc size由128M 增大到了240M),导致没有足够空间给CMA,可以看一下开机打印,修改之后的lowmem为1G整,否则为760M,这和HIGHMEM没有关系。
Memory: 416196K/1048576K available (5240K kernel code, 260K rwdata, 1616K rodata, 200K init, 301K bss, 632380K reserved)
Virtual kernel memory layout:
vector : 0xffff0000 - 0xffff1000 ( 4 kB)
fixmap : 0xfff00000 - 0xfffe0000 ( 896 kB)
vmalloc : 0x80800000 - 0xff000000 (2024 MB)
lowmem : 0x40000000 - 0x80000000 (1024 MB)
modules : 0x3f000000 - 0x40000000 ( 16 MB)
.text : 0x40008000 - 0x406ba454 (6858 kB)
.init : 0x406bb000 - 0x406ed380 ( 201 kB)
.data : 0x406ee000 - 0x4072f320 ( 261 kB)
.bss : 0x4072f32c - 0x4077a7a4 ( 302 kB)
看下设备树分配,这里是共享的,参考dts中memreserve和reserved-memory的区别 ,reserved-memory有一些可选参数,比如no-map,如果使用了no-map,那么这段区域执行memblock_remove,反之执行memblock_reserve。在调用完memblock_reserve后,还会执行fdt_init_reserved_mem。如果reserved-memory下节点的compatible=,则这块内存会被用来进行Contiguous Memory Allocator for dma。initfn对应drivers/base/dma-contiguous.c下的rmem_cma_setup以及drivers/base/dma-coherent.c中的rmem_dma_setup,由于二者的compatible相同,所以前者优先。rmem_cma_setup会对这块内存做初始化,把这块区域加到cma_areas[cma_area_count]中,cma_areas保存着所有的CMA区域,稍后core_init_reserved_areas会对这个数组进行处理。
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
ipu_cma@90000000 {
compatible = "shared-dma-pool";
reg = <0x90000000 0x4000000>;
reusable;
status = "okay";
};
cma_region: region@6a000000 {
compatible = "shared-dma-pool";
no-map;
reg = <0x6a000000 0x1000000>;
linux,cma-default;
};
};
zynq下的例子,
reserved-memory {
#address-cells = <0x1>;
#size-cells = <0x1>;
ranges;
linux,cma {
compatible = "shared-dma-pool";
reusable;
reg = <0x2f000000 0x10000000>;
/*size = <0x8000000>;
alignment = <0x1000>;*/
linux,cma-default;
};
};
cmem {
compatible = "qe,cmem-dev";
};
1GB内存,此处在末尾预留了16MB,
/* global autoconfigured region for contiguous allocations */
linux,cma {
compatible = "shared-dma-pool";
reusable;
size = <0x4000000>;
alignment = <0x2000>;
linux,cma-default;
};
否则会报错,
rmem_cma_setup line256 base[0x374d3000] size[0x8000000] mask[0x3fffff]
Reserved memory: incorrect alignment of CMA region
后续调试zynqmp发现uboot未配置MAPSZ,导致内核启动错误,受到启发,更新zynq配置,cma错误消除,可以看到预留地址变成0x30000000,
Reserved memory: created CMA memory pool at 0x30000000, size 256 MiB
Reserved memory: initialized node linux,cma, compatible id shared-dma-pool
Memory policy: Data cache writealloc
On node 0 totalpages: 262144
free_area_init_node: node 0, pgdat 40702b00, node_mem_map 6f778000
Normal zone: 2048 pages used for memmap
Normal zone: 0 pages reserved
Normal zone: 262144 pages, LIFO batch:31
PERCPU: Embedded 9 pages/cpu @6f759000 s8128 r8192 d20544 u36864
pcpu-alloc: s8128 r8192 d20544 u36864 alloc=9*4096
pcpu-alloc: [0] 0 [0] 1
Built 1 zonelists in Zone order, mobility grouping on. Total pages: 260096
cma预留内存源代码在of_reserved_mem.c,
static int __init __reserved_mem_alloc_size(unsigned long node,
const char *uname, phys_addr_t *res_base, phys_addr_t *res_size)
{
int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
phys_addr_t start = 0, end = 0;
phys_addr_t base = 0, align = 0, size;
int len;
const __be32 *prop;
int nomap;
int ret;
prop = of_get_flat_dt_prop(node, "size", &len);
if (!prop)
return -EINVAL;
if (len != dt_root_size_cells * sizeof(__be32)) {
pr_err("invalid size property in '%s' node.\n", uname);
return -EINVAL;
}
size = dt_mem_next_cell(dt_root_size_cells, &prop);
nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
prop = of_get_flat_dt_prop(node, "alignment", &len);
if (prop) {
if (len != dt_root_addr_cells * sizeof(__be32)) {
pr_err("invalid alignment property in '%s' node.\n",
uname);
return -EINVAL;
}
align = dt_mem_next_cell(dt_root_addr_cells, &prop);
}
/* Need adjust the alignment to satisfy the CMA requirement */
if (IS_ENABLED(CONFIG_CMA)
&& of_flat_dt_is_compatible(node, "shared-dma-pool")
&& of_get_flat_dt_prop(node, "reusable", NULL)
&& !of_get_flat_dt_prop(node, "no-map", NULL)) {
unsigned long order =
max_t(unsigned long, MAX_ORDER - 1, pageblock_order);
align = max(align, (phys_addr_t)PAGE_SIZE << order);
}
...
}