http://blog.csdn.net/hongzg1982/article/details/50244495
某些驱动需要用到一大块连续的物理内存,但使用kmalloc等很分配很大的连续内存。
所以这里有一种三星实现叫CMA的方式,来连续的大内存分配。
Issue 1: Camera, Video Codec等Multimedia Device需要连续的数MB大小的Memory,但kmalloc/alloc_page不能保证分配到连续的数MB或者更大的内存
Issue 2: 在Booting Time预留内存的方式也可以(像早前的PMEM方式),但这么预留的Memory只能被特定的Device驱动所使用,System不能分配这部分内容。如果该Device不用或者所使用的内存没有预留的大的时候,就造成Memory浪费。
Issue 3: Multimedia Device之间需要共享Reserve的Memory
E.g: Camera(FIMC)利用Video Codec(MFC)去Endoding的时候,需要从FIMC分配MFC Reserve的Memory。
Issue 1: 可以分配连续的大的内存(Device需要的连续的大的内存,可以在Boot的时候进行Reserve之后再进行分配)
Issue 2: 防止Reserve方式的Memory浪费
1) 支持Migration功能,所以即使是被某个驱动设备Reserve的区域,在驱动没有使用的时候System可以对该段内存进行分配使用
2) 在System使用这段内存的时候,如果驱动要求分配这个预留的内存,System memory就会被Migration到其他内存区域,之后这段内存被分配给驱动设备
Issue 3: 驱动设备间的内存共享(通过CMA被Reserve的内存会通过CMA进行管理,所以可以驱动设备间共享该段,比如FIMC可以共享MFC预留的内存区域等)
在系统启动的时候,整个物理内存保存在memblock变量里。通过在sanity_check_meminfo()函数里添加log来打印memblock的内容,这里可以看到整个物理内存大小为1.5GB。
<6>[0.000000] [0:swapper:0] sanity_check_meminfo memblock.memory.cnt=2
<6>[0.000000] [0:swapper:0] pys_addr vmalloc_limit = 0xa9c00000
<6>[0.000000] [0:swapper:0] count = 1 , reg->base =0x80000000 , reg->size =0x2fd00000
<6>[0.000000] [0:swapper:0] count = 2 , reg->base =0xb0000000 , reg->size =0x30000000
<6>[0.000000] [0:swapper:0] arm_lowmem_limit =0xa9c00000
之后在arm_memblock_init()->dma_contiguous_reserve()函数中,读取device tree相关的设置来预留内存给CMA
void __init arm_memblock_init(const struct machine_desc *mdesc) {
/* Register the kernel text, kernel data and initrd with memblock. */
#ifdef CONFIG_XIP_KERNEL
memblock_reserve(__pa(_sdata), _end - _sdata);
#else
memblock_reserve(__pa(_stext), _end - _stext); //预留内核代码区域
#endif
#ifdef CONFIG_BLK_DEV_INITRD //预留initramfs区域
if (phys_initrd_size &&
!memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) {
pr_err("INITRD: 0x%08lx+0x%08lx is not a memory region - disabling initrd\n",
phys_initrd_start, phys_initrd_size);
phys_initrd_start = phys_initrd_size = 0;
}
if (phys_initrd_size &&
memblock_is_region_reserved(phys_initrd_start, phys_initrd_size)) {
pr_err("INITRD: 0x%08lx+0x%08lx overlaps in-use memory region - disabling initrd\n",
phys_initrd_start, phys_initrd_size);
phys_initrd_start = phys_initrd_size = 0;
}
if (phys_initrd_size) {
memblock_reserve(phys_initrd_start, phys_initrd_size);
/* Now convert initrd to virtual addresses */
initrd_start = __phys_to_virt(phys_initrd_start);
initrd_end = initrd_start + phys_initrd_size;
}
#endif
arm_mm_memblock_reserve();//预留page table区域
/* reserve any platform specific memblock areas */
if (mdesc->reserve)
mdesc->reserve();
early_init_fdt_scan_reserved_mem();//??reserved-memory什么的,但现在好像不用了
/*
* reserve memory for DMA contigouos allocations,
* must come from DMA area inside low memory
*/
//这个就是读取相关的device tree来预留相关的内存的
dma_contiguous_reserve(min(arm_dma_limit, arm_lowmem_limit));
arm_memblock_steal_permitted = false;
memblock_allow_resize();
memblock_dump_all();
}
以下看dma_contiguous_reserve()函数都怎么读device tree并预留内存给cma的
void __init dma_contiguous_reserve(phys_addr_t limit)
{
phys_addr_t sel_size = 0;
int i;
#ifdef CONFIG_OF
of_scan_flat_dt(cma_fdt_scan, NULL); //这里就是读取device tree的内容的,具体看下面的函数说明
#endif
pr_debug("%s(limit %pa)\n", __func__, &limit);
if (size_cmdline != -1) {
sel_size = size_cmdline;
} else {
//这里留8MB,
//cma: CMA: reserved 8 MiB at 0xa9400000 for default region
#ifdef CONFIG_CMA_SIZE_SEL_MBYTES
sel_size = size_bytes;
#elif defined(CONFIG_CMA_SIZE_SEL_PERCENTAGE)
sel_size = cma_early_percent_memory();
#elif defined(CONFIG_CMA_SIZE_SEL_MIN)
sel_size = min(size_bytes, cma_early_percent_memory());
#elif defined(CONFIG_CMA_SIZE_SEL_MAX)
sel_size = max(size_bytes, cma_early_percent_memory());
#endif
}
//再调用sanity_check_meminfo()
dma_contiguous_early_removal_fixup();
allow_memblock_alloc = true;
for (i = 0; i < cma_area_count; i++) {
if (cma_areas[i].base == 0) {
int ret;
ret = __dma_contiguous_reserve_memory(
cma_areas[i].size,
cma_areas[i].alignment,
cma_areas[i].limit,
&cma_areas[i].base);
if (ret) {
pr_err("CMA: failed to reserve %ld MiB for %s\n",
(unsigned long)cma_areas[i].size / SZ_1M,
cma_areas[i].name);
memmove(&cma_areas[i], &cma_areas[i+1],
(cma_area_count - i)*sizeof(cma_areas[i]));
cma_area_count--;
i--;
continue;
}
//没有基地址的部分,都通过__dma_contiguous_reserve_memory分配
//地址之后,保存到dma_mmu_remap区域中!!
dma_contiguous_early_fixup(cma_areas[i].base,
cma_areas[i].size);
}
pr_info("CMA: reserved %ld MiB at %pa for %s\n",
(unsigned long)cma_areas[i].size / SZ_1M,
&cma_areas[i].base, cma_areas[i].name);
}
if (sel_size) {
phys_addr_t base = 0;
pr_debug("%s: reserving %ld MiB for global area\n", __func__,
(unsigned long)sel_size / SZ_1M);
//由于已经使能了CONFIG_CMA_SIZE_SEL_MBYTES,
//而且CONFIG_CMA_SIZE_MBYTES = 8
//所以会打印打印的 cma: CMA: reserved 8 MiB at 0xa9400000 for default region
if (dma_contiguous_reserve_area(sel_size, &base, limit, NULL,
CMA_RESERVE_AREA ? 0 : 1, false) == 0) {
pr_info("CMA: reserved %ld MiB at %pa for default region\n",
(unsigned long)sel_size / SZ_1M, &base);
dma_contiguous_def_base = base;
}
}
}
//
int __init cma_fdt_scan(unsigned long node, const char *uname,
int depth, void *data)
{
phys_addr_t base, size;
int len;
const __be32 *prop;
const char *name;
bool in_system;
bool remove;
unsigned long size_cells = dt_root_size_cells;
unsigned long addr_cells = dt_root_addr_cells;
phys_addr_t limit = MEMBLOCK_ALLOC_ANYWHERE;
const char *status;
//每个需要预留的内存必须有"linux,reserve-contiguous-region"!!
//看实际的device tree也是,每个部分都有"linux,reserve-contiguous-region"
if (!of_get_flat_dt_prop(node, "linux,reserve-contiguous-region", NULL))
return 0;
//查看是否有status,没有就算了,但如果有的话,必须是"ok",不然这个区域就不会被读取
status = of_get_flat_dt_prop(node, "status", NULL);
/*
* Yes, we actually want strncmp here to check for a prefix
* ok vs. okay
*/
if (status && (strncmp(status, "ok", 2) != 0))
return 0;
prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
if (prop)
size_cells = be32_to_cpup(prop);
prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
if (prop)
addr_cells = be32_to_cpup(prop);
prop = of_get_flat_dt_prop(node, "reg", &len);
if (!prop || depth != 2)
return 0;
base = dt_mem_next_cell(addr_cells, &prop);
size = dt_mem_next_cell(size_cells, &prop);
name = of_get_flat_dt_prop(node, "label", NULL);
//以下读取几个重要的设置,这些会在dma_contiguous_reserve_area()函数中被用到,
in_system =
of_get_flat_dt_prop(node, "linux,reserve-region", NULL) ? 0 : 1;
prop = of_get_flat_dt_prop(node, "linux,memory-limit", NULL);
if (prop)
limit = be32_to_cpu(prop[0]);
remove =
of_get_flat_dt_prop(node, "linux,remove-completely", NULL) ? 1 : 0;
pr_info("Found %s, memory base %pa, size %ld MiB, limit %pa\n", uname,
&base, (unsigned long)size / SZ_1M, &limit);
//
dma_contiguous_reserve_area(size, &base, limit, name,
in_system, remove);
return 0;
}
//cma_fdt_scan()函数读取的device tree内容
memory {
#address-cells = <2>;
#size-cells = <2>;
/* Additionally Reserved 6MB for TIMA and Increased the TZ app size
* by 2MB [total 8 MB ]
*/
//高通平台trustzone存放的地址,如果这个地址改了,必须要修改
//TZBSP_EBI1_TZ_APP_BASE和TZBSP_EBI1_TZ_APP_END等的地址
//不然kernel启动不了!!
external_image_mem: external_image__region@0 {
linux,reserve-contiguous-region;
linux,reserve-region;
linux,remove-completely;
reg = <0x0 0x85500000 0x0 0x01300000>;
label = "external_image_mem";
};
//高通平台(msm8916)modem的存放地址开始地址和大小取决于
//modem binary
modem_adsp_mem: modem_adsp_region@0 {
linux,reserve-contiguous-region;
linux,reserve-region;
linux,remove-completely;
reg = <0x0 0x86800000 0x0 0x05800000>;
label = "modem_adsp_mem";
};
//这个部分是wcnss相关的存放地址,这部分内存需要紧挨着modem内存。
//modem大小修改的时候,这个内存区域的开始地址也需要相应调整
peripheral_mem: pheripheral_region@0 {
linux,reserve-contiguous-region;
linux,reserve-region;
linux,remove-completely;
reg = <0x0 0x8C000000 0x0 0x0600000>;
label = "peripheral_mem";
};
venus_mem: venus_region@0 {
linux,reserve-contiguous-region;
linux,reserve-region;
linux,remove-completely;
reg = <0x0 0x8C600000 0x0 0x0500000>;
label = "venus_mem";
};
secure_mem: secure_region@0 {
linux,reserve-contiguous-region;
reg = <0 0 0 0x6D00000>;
label = "secure_mem";
};
qseecom_mem: qseecom_region@0 {
linux,reserve-contiguous-region;
reg = <0 0 0 0xD00000>;
label = "qseecom_mem";
};
audio_mem: audio_region@0 {
linux,reserve-contiguous-region;
reg = <0 0 0 0x314000>;
label = "audio_mem";
};
cont_splash_mem: splash_region@8E000000 {
linux,reserve-contiguous-region;
linux,reserve-region;
reg = <0x0 0x8E000000 0x0 0x1400000>;
label = "cont_splash_mem";
};
};
以下简单说一下高通msm8916平台,modem大小检查以及修改方法。
1) modem binary的大小可以从以下编译的log里边看出来!!(modem_proc/build/ms目录下的pplk-XXX.log或者build_xxxx.log)。
根据大小对齐1MB大小,就是modem binary需要流出来的大小。看如下例子里边的log,总的大小是77.04,
所以需要在上面的dtsi文件中留出来78MB就可以。
Image loaded at virtual address 0xc0000000
Image: 55.44 MiB
AMSS Heap: 7.50 MiB (dynamic)
MPSS Heap: 4.00 MiB (dynamic)
DSM Pools: 5.06 MiB
Q6Zip RO, Swap Pool: 2.00 MiB (dynamic)
Q6Zip RW, Swap Pool: 1.00 MiB (dynamic)
Q6Zip RW, dlpager Heap: 1.00 MiB
Extra: 0.54 MiB
Pad ding: 0.37 MiB
End Address Alignment: 0.13 MiB
Total: 77.04 MiB
Available: 7.96 MiB
2) 然后去修改modem_proc/config/xxx/ 目录下的cust_config.xml文件中修改modem大小
"DEFAULT_PHYSPOOL">
"0x88000000" size="0x5500000" />
"0x88000000" size="0x4E00000" />
3) 重新编译,然后在第一步中编译出来的文件中重新确认大小
以下就是实际操作cma_areas变量的函数
//有"linux,reserve-region"的区域 to_system就是false
//有"linux,remove-completely"的区域,remove就是true
int __init dma_contiguous_reserve_area(phys_addr_t size, phys_addr_t *res_base,
phys_addr_t limit, const char *name,
bool to_system, bool remove)
{
phys_addr_t base = *res_base;
phys_addr_t alignment = PAGE_SIZE;
int ret = 0;
pr_debug("%s(size %lx, base %pa, limit %pa)\n", __func__,
(unsigned long)size, &base,
&limit);
/* Sanity checks */
if (cma_area_count == ARRAY_SIZE(cma_areas)) {
pr_err("Not enough slots for CMA reserved regions!\n");
return -ENOSPC;
}
if (!size)
return -EINVAL;
/* Sanitise input arguments */
if (!remove)
alignment = PAGE_SIZE << max(MAX_ORDER - 1, pageblock_order);
base = ALIGN(base, alignment);
size = ALIGN(size, alignment);
limit &= ~(alignment - 1);
//有base地址的设置,如果不与其他预留的地址冲突的话,就调用memblock_reset从memblock.memory中去掉!!
//所以上面device tree设置中external_image_mem,modem_adsp_mem,peripheral_mem,venus_mem被
//直接从memblock给去掉了,,这个可以看一下前后对比的memblock打印的内容
/* Reserve memory */
if (base) {
if (memblock_is_region_reserved(base, size) ||
memblock_reserve(base, size) < 0) {
ret = -EBUSY;
goto err;
}
} else {
//如果是没有指定base地址的,就要通过__dma_contiguous_reserve_memory()函数
//从memblock相关的接口分配到满足大小的地址,并返回base地址了
ret = __dma_contiguous_reserve_memory(size, alignment, limit,
&base);
if (ret)
goto err;
}
//有base地址,有"linux,remove-completely",但没有"linux,reserve-region"就会报错!
//所以查看上面的device tree内容,external_image_mem,modem_adsp_mem,
//peripheral_mem,venus_mem等有base地址的都是既有"linux,remove-completely"
//又有"linux,reserve-region"的就会跑到下面的if(!to_system)内
if (base && remove) {
if (!to_system) {
//这里边就是把上面的区域从memblock.memory里边去掉,加到membloc.reserve里边等操作的
memblock_free(base, size);
memblock_remove(base, size);
} else {
WARN(1, "Removing is incompatible with staying in the system\n");
}
}
/*
* Each reserved area must be initialised later, when more kernel
* subsystems (like slab allocator) are available.
*/
cma_areas[cma_area_count].base = base;
cma_areas[cma_area_count].size = size;
cma_areas[cma_area_count].name = name;
cma_areas[cma_area_count].alignment = alignment;
cma_areas[cma_area_count].limit = limit;
cma_areas[cma_area_count].to_system = to_system;
cma_area_count++;
*res_base = base;
//以下是有base地址,且没有定义"linux,remove-completely"的
//比如fb相关的,上面的定义是"cont_splash_mem"
//这些内存都会调用dma_contiguous_early_fixup()函数
//这个函数很简单,就是把相关的内存都保存在dma_mmu_remap中。
//有多少区域,用dma_mmu_remap_num来表示。
/* Architecture specific contiguous memory fixup. */
if (!remove && base)
dma_contiguous_early_fixup(base, size);
return 0;
err:
pr_err("CMA: failed to reserve %ld MiB\n", (unsigned long)size / SZ_1M);
return ret;
}
//这个函数可以总结一些规律:
//举例来说,像msm8916这种,如果不是AP这边控制的内存,像modem相关的内存,有base地址
//而且都是定义了remove,从内存中整个去掉,因为不是AP这边需要控制的部分,只要预留出来
//就好。像external_image_mem,modem_adsp_mem,peripheral_mem,venus_mem就是这样的。
//这些部分看上面的函数,就是直接从memblock.memory中挖掉,放到memblock.reserve里。
//不会在paging_init的时候被map到lowmemory里边。
//但frambuffer对应的内存(cont_splash_mem),既要预留,又要AP这边控制的,就只定义了base地址,但没有定义
//"linux,remove-completely"来去掉这部分内存!!这种会通过dma_contiguous_early_fixup()把这部分内存
//放到dma_mmu_remap中!!这部分会在paging_init()->dma_contiguous_remap()中map起来(这部分在dma部分细说)
//当然cma_areas和cma_area_count是会保存所有上面所说的内容的!!
<6>[0.000000] [0:swapper:0] sanity_check_meminfo memblock.memory.cnt=2
<6>[0.000000] [0:swapper:0] pys_addr vmalloc_limit = 0xa9c00000
<6>[0.000000] [0:swapper:0] count = 1 , reg->base =0x80000000 , reg->size =0x2fd00000
<6>[0.000000] [0:swapper:0] count = 2 , reg->base =0xb0000000 , reg->size =0x30000000
<6>[0.000000] [0:swapper:0] arm_lowmem_limit =0xa9c00000
<6>[0.000000] [0:swapper:0] cma: Found external_image__region@0, memory base 0x85500000, size 19 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] cma: Found modem_adsp_region@0, memory base 0x86800000, size 88 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] cma: Found pheripheral_region@0, memory base 0x8c000000, size 6 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] cma: Found venus_region@0, memory base 0x8c600000, size 5 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] cma: Found secure_region@0, memory base 0x00000000, size 109 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] cma: Found qseecom_region@0, memory base 0x00000000, size 13 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] cma: Found audio_region@0, memory base 0x00000000, size 3 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] cma: Found splash_region@8E000000, memory base 0x8e000000, size 20 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] sanity_check_meminfo memblock.memory.cnt=3
<6>[0.000000] [0:swapper:0] pys_addr vmalloc_limit = 0xa9c00000
<6>[0.000000] [0:swapper:0] count = 1 , reg->base =0x80000000 , reg->size =0x5500000
<6>[0.000000] [0:swapper:0] count = 2 , reg->base =0x8cb00000 , reg->size =0x23200000
<6>[0.000000] [0:swapper:0] count = 3 , reg->base =0xb0000000 , reg->size =0x30000000
<6>[0.000000] [0:swapper:0] arm_lowmem_limit =0xb1200000
<6>[0.000000] [0:swapper:0] cma: CMA: reserved 19 MiB at 0x85500000 for external_image_mem
<6>[0.000000] [0:swapper:0] cma: CMA: reserved 88 MiB at 0x86800000 for modem_adsp_mem
<6>[0.000000] [0:swapper:0] cma: CMA: reserved 6 MiB at 0x8c000000 for peripheral_mem
<6>[0.000000] [0:swapper:0] cma: CMA: reserved 5 MiB at 0x8c600000 for venus_mem
<6>[0.000000] [0:swapper:0] cma: CMA: reserved 112 MiB at 0xd9000000 for secure_mem
<6>[0.000000] [0:swapper:0] cma: CMA: reserved 16 MiB at 0xd8000000 for qseecom_mem
<6>[0.000000] [0:swapper:0] cma: CMA: reserved 4 MiB at 0xd7c00000 for audio_mem
<6>[0.000000] [0:swapper:0] cma: CMA: reserved 20 MiB at 0x8e000000 for cont_splash_mem
<6>[0.000000] [0:swapper:0] cma: CMA: reserved 8 MiB at 0xa9400000 for default region
<4>[0.000000] [0:swapper:0] Memory policy: ECC disabled, Data cache writealloc
相关的接口都在dma-contiguous.c文件里边
//以下函数为每个cma_areas初始化cma并保存到cma_areas[i].cma里边
//cma的定义如下:
struct cma {
unsigned long base_pfn;
unsigned long count;
unsigned long *bitmap;
bool in_system;
struct mutex lock;
};
static int __init cma_init_reserved_areas(void)
{
struct cma *cma;
int i;
for (i = 0; i < cma_area_count; i++) {
phys_addr_t base = PFN_DOWN(cma_areas[i].base);
unsigned int count = cma_areas[i].size >> PAGE_SHIFT;
bool system = cma_areas[i].to_system;
cma = cma_create_area(base, count, system);
if (!IS_ERR(cma))
cma_areas[i].cma = cma;
}
//默认的8MB的cma区域保存在dma_contiguous_def_area里边,
//这个cma区域在dev_get_cma_area()函数中,如果没有找到dev对应的cma区域的话,就会使用
//dma_contiguous_def_area这个默认的cma区域
dma_contiguous_def_area = cma_get_area(dma_contiguous_def_base);
for (i = 0; i < cma_map_count; i++) {
cma = cma_get_area(cma_maps[i].base);
dev_set_cma_area(cma_maps[i].dev, cma);
}
//注册platform_bus_type的notifier函数,在每个platform设备驱动注册的时候,检查是否有
//"linux,contiguous-region",有的话会根据相应的名字分配对应的cma并保存到dev->cma里边
//这个在后面有具体说明
#ifdef CONFIG_OF
bus_register_notifier(&platform_bus_type, &cma_dev_init_nb);
#endif
return 0;
}
core_initcall(cma_init_reserved_areas);
bus_register_notifier(&platform_bus_type, &cma_dev_init_nb) 这样注册platform_bus的notifier函数之后,
有以下platform设备在注册的时候,会通过cma_assign_device_from_dt()函数找到相应的cma区域并
赋值给dev->cma
static void cma_assign_device_from_dt(struct device *dev)
{
struct device_node *node;
struct cma *cma;
const char *name;
u32 value;
//找到相应的device tree设置里边有没有"linux,contiguous-region",
node = of_parse_phandle(dev->of_node, "linux,contiguous-region", 0);
if (!node)
return;
if (of_property_read_u32(node, "reg", &value) && !value)
return;
//找到label的名字
if (of_property_read_string(node, "label", &name))
return;
//根据名字找到cma area
cma = cma_get_area_by_name(name);
if (!cma)
return;
//cma赋值给dev->cma_area
dev_set_cma_area(dev, cma);
//如果有"linux,remove-completely",就把removed_dma_ops赋值给
//dev->archdata.dma_ops = removed_dma_ops
if (of_property_read_bool(node, "linux,remove-completely"))
set_dma_ops(dev, &removed_dma_ops);
pr_info("Assigned CMA region at %lx to %s device\n", (unsigned long)value, dev_name(dev));
}
以下是所有有cma区域的platform设备在初始化的时候找到的cma区域
<6>[0.487642] [0:swapper/0:1] cma: Assigned CMA region at 0 to 1de0000.qcom,venus device
<6>[0.489469] [0:swapper/0:1] cma: Assigned CMA region at 0 to 4080000.qcom,mss device
<6>[0.490756] [0:swapper/0:1] cma: Assigned CMA region at 0 to a21b000.qcom,pronto device
<6>[1.125342] [0:swapper/0:1] cma: Assigned CMA region at 0 to 8.qcom,ion-heap device
<6>[1.125793] [0:swapper/0:1] cma: Assigned CMA region at 0 to 1b.qcom,ion-heap device
<6>[1.126233] [0:swapper/0:1] cma: Assigned CMA region at 0 to 1c.qcom,ion-heap device
<6>[1.126671] [0:swapper/0:1] cma: Assigned CMA region at 0 to 17.qcom,ion-heap device
<6>[1.127298] [0:swapper/0:1] cma: Assigned CMA region at 0 to 1a.qcom,ion-heap device
这些CMA划分出去的区域,除了external_image_mem是存放trustzone的区域不用管之外,其他的由msm_ion.c统一管理。这个在ION Memory相关的说明中再说。。。