问题场景:
arm64 开启了CONFIG_ZONE_DMA32,其区域默认是offset ~ offset+4G,其中offset一般为0,但是部分驱动无法完全访问到4G,需要限制DMA32区域的范围为0-2G。
qemu提取dts文件
尝试在qemu上调整一下ZONE_DMA32的大小,按照如下步骤导出qemu的dts文件:
./dtc -o dump.dts -O dts -I dtb dump.dtb
qemu-system-aarch64 -machine virt,dumpdtb=dump.dtb
cp dump.dtb linux/scripts/dtc/
cd linux/scripts/dtc/
make 这步如果内核已经编译过了,可以跳过。
./dtc -o dump.dts -O dts -I dtb dump.dtb
生成的dts文件之后打开可见如下内存范围为0x40000000-0x80000000默认是1~2G内存范围,
359 memory {
360 reg = <0x00 0x40000000 0x00 0x8000000>;
361 device_type = "memory";
362 };
通过qemu的cmdline可以传入例如8192m以调整内存大小。
内核调整DMA ZONE的关键节点
对于x8664位系统:
x86_64 needs two ZONE_DMAs because it supports devices that are only able to do DMA to the lower 16M but also 32 bit devices that can only do DMA areas below 4G.
而对于arm64位系统:只有一个ZONE_DMA32 ,在zone_sizes_init会确定ZONE DMA32、ZONE_NORMAL的大小,DMA32的尽头就是NORMAL的开始。max_dma即 PFN_DOWN(arm64_dma_phys_limit),那么只要调整arm64_dma_phys_limit即可调整ZONE_DMA32的范围。
static void __init zone_sizes_init(unsigned long min, unsigned long max)
{
struct memblock_region *reg;
unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES];
unsigned long max_dma = min;
memset(zone_size, 0, sizeof(zone_size));
/* 4GB maximum for 32-bit only capable devices */
#ifdef CONFIG_ZONE_DMA32
max_dma = PFN_DOWN(arm64_dma_phys_limit);
zone_size[ZONE_DMA32] = max_dma - min;
#endif
zone_size[ZONE_NORMAL] = max - max_dma;
memcpy(zhole_size, zone_size, sizeof(zhole_size));
。。。
}
通过cmdline来定制ZONE_DMA32的最大值,修改diff如下:
+static unsigned long dma_reset_end = 0;
+static int __init dma_reset_end_setup(char *str)
+{
+ static int once;
+ if (once)
+ return 0;
+ once = 1;
+ dma_reset_end = memparse(str, &str);
+ printk("dma_reset_end=0x%lx\n", dma_reset_end);
+ return 0;
+}
/*
* Return the maximum physical address for ZONE_DMA32 (DMA_BIT_MASK(32)). It
* currently assumes that for memory starting above 4G, 32-bit devices will
* use a DMA offset.
*/
static phys_addr_t __init max_zone_dma_phys(void)
{
+ phys_addr_t max;
phys_addr_t offset = memblock_start_of_DRAM() & GENMASK_ULL(63, 32);
- return min(offset + (1ULL << 32), memblock_end_of_DRAM());
-}
+ max = min(offset + (1ULL << 32), memblock_end_of_DRAM());
+ if (dma_reset_end)
+ max = min(max, dma_reset_end);
+ return max;
+}
在cmdline增加dma_reset_end定制
Kernel command line: root=/dev/sda rodata=off rdinit=/usr/sbin/init kgdboc=ttyAMA0,115200 rootfstype=ramfs loglevel=7 slub_debug=u dma_reset_end=0x90000000 console=ttyAMA0 rw
修改效果:
修改前:
[ 0.000000] Zone ranges:
[ 0.000000] DMA32 [mem 0x0000000040000000-0x00000000ffffffff]——范围1G-4G
[ 0.000000] Normal [mem 0x0000000100000000-0x000000023fffffff]——范围4G-9G
[ 0.000000] Movable zone start for each node
[ 0.000000] Early memory node ranges
[ 0.000000] node 0: [mem 0x0000000040000000-0x000000023fffffff]
[ 0.000000] Initmem setup node 0 [mem 0x0000000040000000-0x000000023fffffff]
[ 0.000000] On node 0 totalpages: 131072
[ 0.000000] DMA32 zone: 60 pages used for memmap
[ 0.000000] DMA32 zone: 0 pages reserved
[ 0.000000] DMA32 zone: 49152 pages, LIFO batch:3
[ 0.000000] Normal zone: 100 pages used for memmap
[ 0.000000] Normal zone: 81920 pages, LIFO batch:3
[ 0.000000] Kernel command line: root=/dev/sda rodata=off rdinit=/usr/sbin/init kgdboc=ttyAMA0,115200 rootfstype=ramfs loglevel=7 slub_debug=u console=ttyAMA0 rw
修改后:
[ 0.000000] max_dma_phys have been modified. old:0x100000000 new:0x90000000
[ 0.000000] Zone ranges:
[ 0.000000] DMA32 [mem 0x0000000040000000-0x000000008fffffff]——范围是1G-2.25G
[ 0.000000] Normal [mem 0x0000000090000000-0x000000023fffffff]——范围2.25G-9G
[ 0.000000] Movable zone start for each node
[ 0.000000] Kernel command line: root=/dev/sda rodata=off rdinit=/usr/sbin/init kgdboc=ttyAMA0,115200 rootfstype=ramfs loglevel=7 slub_debug=u dma_reset_end=0x90000000 console=ttyAMA0 rw