文章目录
- 前言
- 概念
- 功能启用
- CMA 内存的创建
- 方式一、使用 cmdline
- 方式二、使用 dts
- CMA 内存分配和释放
- 实例(dts 方式)
前言
在嵌入式设备中,很多外设(如摄像机、硬件视频解码器等)需要较大的内存缓冲区,kmalloc 内存分配机制对于这么大的内存是没有效果的。
早期的 Linux 内核中,如果驱动想要申请一大块的物理连续内存,那么只能通过预留专属内存的形式,然后在驱动中使用 ioremap 来映射后作为私有内存使用。这样带来的后果就是有一部分内存将被预留出来不能作为系统中的通用内存来使用。比如 camera、audio 设备,它们在工作时是需要大块连续内存进行 DMA 操作的,而当这些设备不工作时,预留的内存也无法被其它模块使用。
引入 CMA 就是为了解决这个问题的,定义为 CMA 区域的内存,也是由操作系统来管理的,当一个驱动模块想要申请大块连续内存时,通过内存管理子系统把 CMA 区域的内存进行迁移,空出连续内存给驱动使用;而当驱动模块释放这块连续内存后,它又被归还给操作系统管理,可以给其它申请者分配使用。
概念
CMA 全称叫做 continuous memory allocator,它是为了便于进行连续物理内存申请的一块区域,一般我们把这块区域定义为 reserved-memory。
功能启用
Linux 内核配置 CONFIG_CMA=y
CMA 内存的创建
方式一、使用 cmdline
# cat /proc/cmdline
root=PARTUUID=2d2a225f-07d8-4811-9802-314074f963ff rootwait console=ttyS0,115200 rootfstype=ext4 quiet panic=10 cma=12M@0x7e000000
详细参数
cma=nn[MG]@[start[MG][-end[MG]]]
[ARM,X86,KNL]
Sets the size of kernel global memory area for
contiguous memory allocations and optionally the
placement constraint by the physical address range of
memory allocations. A value of 0 disables CMA
altogether. For more information, see
include/linux/dma-contiguous.h
方式二、使用 dts
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
reserved: buffer@0 {
compatible = "shared-dma-pool";
reusable;
reg = <0x60000000 0x01000000>;
linux,cma-default;
};
reserved2: buffer@2 {
compatible = "shared-dma-pool";
reusable;
reg = <0x62000000 0x02000000>;
linux,cma-default;
};
};
compatible 的值必须是 “shared-dma-pool” 才能被解析成 CMA 类型的内存。
一定要使用 reusable 选项,才会被当作 CMA 内存,如果使用 no-map 则会被当作 DMA 内存。
如果使能了 linux,cma-default; 选项,则不会出现 cma: Reserved 16 MiB at 0x7e000000;否则,会出现。
CMA 内存分配和释放
当一个内核模块要使用 CMA 内存时,使用的接口依然是 DMA 的接口
extern void *
dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle,
gfp_t flag);
extern void
dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,
dma_addr_t dma_handle);
实例(dts 方式)
硬件:OrangePi PC
arch/arm/boot/dts/sun8i-h3-orangepi-pc.dts
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
reserved: buffer@0 {
compatible = "shared-dma-pool";
reusable;
reg = <0x60000000 0x01000000>;
linux,cma-default;
};
reserved2: buffer@2 {
compatible = "shared-dma-pool";
no-map;
reg = <0x62000000 0x02000000>;
linux,cma-default;
};
};
cma.ko
#include <linux/dma-mapping.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#define ALLOC_SIZE (12 * 1024)
static struct device *cma_dev;
static u32 dma_phys;
static u32 *dma_virt;
static struct miscdevice cma_test_misc = {
.name = "cma_test",
};
static int cma_init(void)
{
int i;
int ret = 0;
printk("cma_init()\n");
ret = misc_register(&cma_test_misc);
if (unlikely(ret)) {
_dev_info(cma_dev, "failed to register cma test misc device!\n");
return ret;
}
cma_dev = cma_test_misc.this_device;
cma_dev->coherent_dma_mask = ~0;
_dev_info(cma_dev, "registered\n");
dma_virt = dma_alloc_coherent(cma_dev, ALLOC_SIZE, &dma_phys, GFP_KERNEL);
if (dma_virt) {
for (i = 0; i < ALLOC_SIZE / sizeof(u32); i++)
dma_virt[i] = 0x12345678;
_dev_info(cma_dev, "alloc virt: 0x%x phys: 0x%x size: 0x%x\n", (u32)dma_virt, dma_phys, ALLOC_SIZE);
} else {
_dev_info(cma_dev, "no mem in CMA area\n");
}
return 0;
}
static void cma_exit(void)
{
_dev_info(cma_dev, "cma_exit()\n");
misc_deregister(&cma_test_misc);
_dev_info(cma_dev, "deregistered\n");
if (dma_virt) {
dma_free_coherent(cma_dev, ALLOC_SIZE, dma_virt, dma_phys);
_dev_info(cma_dev, "free virt: 0x%x phys: 0x%x size: 0x%x\n", (u32)dma_virt, dma_phys, ALLOC_SIZE);
}
}
module_init(cma_init);
module_exit(cma_exit);
MODULE_LICENSE("Dual BSD/GPL");
Makefile
obj-m = cma.o
KDIR = /home/liyongjun/project/board/buildroot/OrangePiPC/build/linux-custom
CROSS_COMPILE = /home/liyongjun/project/board/buildroot/OrangePiPC/host/bin/arm-linux-
EXTRA_CFLAGS = -g
all:
make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules
clean:
make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) clean
install:
cp cma.ko ~/tftp
内核启动 log
[ 0.000000] OF: fdt: Machine model: Xunlong Orange Pi PC
[ 0.000000] Memory policy: Data cache writealloc
[ 0.000000] Reserved memory: created CMA memory pool at 0x60000000, size 16 MiB
[ 0.000000] OF: reserved mem: initialized node buffer@0, compatible id shared-dma-pool
[ 0.000000] Reserved memory: created DMA memory pool at 0x62000000, size 32 MiB
[ 0.000000] OF: reserved mem: initialized node buffer@2, compatible id shared-dma-pool
[ 0.000000] Zone ranges:
[ 0.000000] Normal [mem 0x0000000040000000-0x000000006fffffff]
[ 0.000000] HighMem [mem 0x0000000070000000-0x000000007fffffff]
[ 0.000000] Movable zone start for each node
[ 0.000000] Early memory node ranges
[ 0.000000] node 0: [mem 0x0000000040000000-0x0000000061ffffff]
[ 0.000000] node 0: [mem 0x0000000062000000-0x0000000063ffffff]
[ 0.000000] node 0: [mem 0x0000000064000000-0x000000007fffffff]
[ 0.000000] Initmem setup node 0 [mem 0x0000000040000000-0x000000007fffffff]
[ 0.000000] On node 0 totalpages: 262144
[ 0.000000] Normal zone: 1536 pages used for memmap
[ 0.000000] Normal zone: 0 pages reserved
[ 0.000000] Normal zone: 196608 pages, LIFO batch:63
[ 0.000000] HighMem zone: 65536 pages, LIFO batch:15
[ 0.000000] psci: probing for conduit method from DT.
[ 0.000000] psci: Using PSCI v0.1 Function IDs from DT
[ 0.000000] percpu: Embedded 15 pages/cpu s30988 r8192 d22260 u61440
[ 0.000000] pcpu-alloc: s30988 r8192 d22260 u61440 alloc=15*4096
[ 0.000000] pcpu-alloc: [0] 0 [0] 1 [0] 2 [0] 3
[ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 260608
[ 0.000000] Kernel command line: root=PARTUUID=2d2a225f-07d8-4811-9802-314074f963ff rootwait console=ttyS0,115200 rootfstype=ext4 quiet panic=10
[ 0.000000] Dentry cache hash table entries: 131072 (order: 7, 524288 bytes, linear)
[ 0.000000] Inode-cache hash table entries: 65536 (order: 6, 262144 bytes, linear)
[ 0.000000] mem auto-init: stack:off, heap alloc:off, heap free:off
[ 0.000000] Memory: 977608K/1048576K available (7168K kernel code, 904K rwdata, 1944K rodata, 1024K init, 273K bss, 54584K reserved, 16384K cma-reserved, 262132K highmem)
从内核启动 log 可知,内核在物理地址 0x60000000 处分配了 CMA 内存,大小为 16M。
ko 测试
Welcome to Buildroot for the Orange Pi PC
OrangePi_PC login: root
# tftp -gr cma.ko 192.168.31.223
# echo 7 > /proc/sys/kernel/printk
#
# insmod cma.ko
[ 45.013959] cma: loading out-of-tree module taints kernel.
[ 45.020024] cma_init()
[ 45.022643] misc cma_test: registered
[ 45.026793] misc cma_test: alloc virt: 0xe005c000 phys: 0x6005c000 size: 0x3000
#
# devmem 0x6005c000
0x12345678
#
# devmem 0x6005effc
0x12345678
#
# devmem 0x6005f000
0xFFFFFFFF
#
# rmmod cma
[ 109.603006] misc cma_test: cma_exit()
[ 109.607098] misc cma_test: deregistered
[ 109.610970] misc cma_test: free virt: 0xe005c000 phys: 0x6005c000 size: 0x3000
在 cma.ko 中,使用 CMA API 从 CMA 内存中申请了 12M,即 phys: 0x6005c000 size: 0x3000,并将这块区域按 int 的类型全部赋值为 0x12345678,后续使用 devmem 验证该操作成功。
另外,我们还使用 “no-map” 的方式申请了一部分 reserved-memory 作为 DMA 内存,这点可以通过 /proc/iomem 信息得到验证(内核启动 log 也可以佐证),System RAM 被截断成了两部分,而中间缺失的那部分 0x62000000-0x63ffffff 正是被我们设置成了 reserved-memory。
40000000-61ffffff : System RAM
64000000-7fffffff : System RAM
# cat /proc/iomem
01000000-0100ffff : 1000000.clock clock@1000000
01100000-011fffff : 1100000.mixer mixer@1100000
01400000-0141ffff : 1400000.deinterlace deinterlace@1400000
01c00000-01c00fff : 1c00000.system-control system-control@1c00000
01c02000-01c02fff : 1c02000.dma-controller dma-controller@1c02000
01c0c000-01c0cfff : 1c0c000.lcd-controller lcd-controller@1c0c000
01c0e000-01c0efff : 1c0e000.video-codec video-codec@1c0e000
01c0f000-01c0ffff : 1c0f000.mmc mmc@1c0f000
01c14000-01c143ff : 1c14000.eeprom eeprom@1c14000
01c15000-01c15fff : 1c15000.crypto crypto@1c15000
01c17000-01c17fff : 1c17000.mailbox mailbox@1c17000
01c19000-01c193ff : usb@1c19000
01c19000-01c193ff : musb-hdrc.4.auto usb@1c19000
01c19400-01c1942b : 1c19400.phy phy_ctrl
01c1a000-01c1a0ff : 1c1a000.usb usb@1c1a000
01c1a400-01c1a4ff : 1c1a400.usb usb@1c1a400
01c1a800-01c1a803 : 1c19400.phy pmu0
01c1b000-01c1b0ff : 1c1b000.usb usb@1c1b000
01c1b400-01c1b4ff : 1c1b400.usb usb@1c1b400
01c1b800-01c1b803 : 1c19400.phy pmu1
01c1c000-01c1c0ff : 1c1c000.usb usb@1c1c000
01c1c400-01c1c4ff : 1c1c400.usb usb@1c1c400
01c1c800-01c1c803 : 1c19400.phy pmu2
01c1d000-01c1d0ff : 1c1d000.usb usb@1c1d000
01c1d400-01c1d4ff : 1c1d400.usb usb@1c1d400
01c1d800-01c1d803 : 1c19400.phy pmu3
01c20000-01c203ff : clock@1c20000
01c20800-01c20bff : 1c20800.pinctrl pinctrl@1c20800
01c20ca0-01c20cbf : 1c20ca0.watchdog watchdog@1c20ca0
01c22c00-01c22fff : 1c22c00.codec codec@1c22c00
01c25000-01c253ff : 1c25000.thermal-sensor thermal-sensor@1c25000
01c28000-01c2801f : serial
01c30000-01c3ffff : 1c30000.ethernet ethernet@1c30000
01c40000-01c4ffff : 1c40000.gpu gpu@1c40000
01ee0000-01eeffff : 1ee0000.hdmi hdmi@1ee0000
01ef0000-01efffff : 1ef0000.hdmi-phy hdmi-phy@1ef0000
01f00000-01f003ff : rtc@1f00000
01f01400-01f014ff : clock@1f01400
01f015c0-01f015c3 : 1f015c0.codec-analog codec-analog@1f015c0
01f02000-01f023ff : 1f02000.ir ir@1f02000
01f02400-01f027ff : 1f02400.i2c i2c@1f02400
01f02c00-01f02fff : 1f02c00.pinctrl pinctrl@1f02c00
40000000-61ffffff : System RAM
40008000-40afffff : Kernel code
40c00000-40d26637 : Kernel data
64000000-7fffffff : System RAM