1. 背景

在我们使用ARM等嵌入式Linux系统的时候,一个头疼的问题是GPU,Camera,HDMI等都需要预留大量连续内存,这部分内存平时不用,但是一般的做法又必须先预留着。CMA(Contiguous Memory Allocator)连续内存分配,是一种用于申请大量的,并且物理上连续的内存块的方法,是 Linux Kernel 内存管理系统的扩展,目的在于解决驱动设备需要预留大量连续内存导致运行内存紧张的问题。通过这套机制,我们可以做到不预留内存,这些内存平时是可用的,只有当需要的时候才被分配给Camera,HDMI等设备。

为设备驱动预留内存:linux kernel无法管理这部分内存(不可见),相当于预留多少系统就减少多少内存,预留多了严重影响系统性能。
kmalloc/kfree:一次最大申请量4M,需要大量连续物理内存的时候分配效率不高,极有可能无法成功分配到连续的大内存。
CMA:一次可以申请很大的连续内存,不用的时候可以释放给系统供别的模块使用,避免预留大块内存。

2. 声明CMA连续内存

2.1 start_kernel初始化

在内核启动的过程中会声明CMA内存,具体流程如下:

start_kernel

–>setup_arch

–>arm_memblock_init

–>dma_contiguous_reserve

–>cma_declare_contiguous

–>memblock_alloc_range

最终调用memblock_alloc_range为CMA连续内存申请分配内存空间。

2.2 CMA driver初始化

Cma.c (mm)

early_initcall(cma_init_reserved_areas);

–>cma_activate_area 针对每一个page向下调用

–>init_cma_reserved_pageblock

Page_alloc.c (mm)

cma架构的好处 cma架构怎么样_cma架构的好处


通过set_pageblock_migratetype(page, MIGRATE_CMA)将页设置为MIGRATE_CMA类型。

3. 申请CMA连续内存

3.1 驱动的CMA内存初始化

Of_reserved_mem.h (include\linux)

cma架构的好处 cma架构怎么样_初始化_02


该函数实现与驱动相关的 CMA 内存的初始化。输入参数为 device 结构体。通常在设备驱动的 probe 阶段调用该函数,主要用来挂钩和设备相关的 cma 数据结构。返回 0 表示操作成功。

3.2 驱动申请CMA内存

申请接口位于Dma-contiguous.c (drivers\base):

cma架构的好处 cma架构怎么样_cma架构的好处_03


该函数实现驱动对 CMA 内存的申请。输入参数 dev 为声明 CMA 的设备 device 结构体, count 表示要分配的 page 数量(以 page 为单位分配而非字节),align 参数表示以 2^align 个 page为单位对齐。分配成功则返回连续 page 的起始地址,失败则返回 NULL。

cma架构的好处 cma架构怎么样_cma架构的好处_04


也就是在device driver定义的CMA pool上去分配。

4. 释放CMA连续内存

释放CMA连续内存的接口是:

cma架构的好处 cma架构怎么样_初始化_05


释放dma_alloc_from_contiguous返回的page开始的连续count个page。建议申请和释放配对使用,并且两者的count参数保持一致。

5. CMA使用案例

我们有一颗A55+dsp的SOC,dsp跑的是freeRTOS,dsp需要在A55的dsp driver中启动。于是我们需要在dsp driver端申请一段连续的内存加载dsp firmware,然后在DDR上启动firmware运行dsp。

由于我们这颗SOC需要一直运行dsp,所以driver中暂时不需要对CMA内存的release操作。

5.1 CMA的dts配置

设备必须在devicetree的reserved-memory节点里面增加自己的CMA内存声明:

cma架构的好处 cma架构怎么样_初始化_06


(1)compatible的值必须是"shared-dma-pool"才能被解析成CMA类型的内存。

(2)reusable是必须有的属性,表明这段内存可以共享给应用数据使用。

(3)Size:CMA内存区域的大小,必须以4M为单位对其,根据实际需要以4M的整数倍向上取整。

(4)Alignment:用于指定标记为CMA的物理内存起始地址的对其需求,通常取决于硬件设备,必须按4MB对齐。

(5)Alloc-ranges:指定设备所需CMA的起始物理地址和内存size,有些设备需要工作在特定的物理地址,那么需要配置这个属性。如果不配置这个属性那么kernel会自行分配。

5.2 设备驱动的dts配置

设备必须在自己的节点中添加memory-region属性,并让其指向reserved-memory中声明的节点:

cma架构的好处 cma架构怎么样_cma架构的好处_07


(1)添加memory-region节点指向reserved-memory中自定义的节点dsp_fw_reserved。

(2)添加CMA size方便驱动相关实现。

5.3 申请CMA内存

此驱动在probe中申请并一直不释放该内存:

cma架构的好处 cma架构怎么样_设备驱动_08


(1)该函数实现与驱动相关的 CMA 内存的初始化。主要用来挂钩和设备相关的 cma 数据结构。返回 0 表示操作成功。

(2)获取CMA内存size。

(3)调用dma_alloc_from_contiguous分配CMA内存,返回连续内存的第一个page指针。可以用page_to_phys(cma_pages)得到该CMA内存的物理首地址,进一步用phys_to_virt或者mm_vmap可以得到虚拟地址。在设备驱动中就可以根据需要灵活使用这段CMA内存了。