0. 前言

随着内核的运行,内核中的物理内存越来越趋向于碎片化,但是某些特定的设备在使用时用到的DMA需要大量的连续物理内存,这可能导致设备在真正使用的时候因为申请不到满足要求的物理内存而无法使用,这显然是不能接受的。

最简单的方式就是为特定设备预留一部分物理内存专用,这部分内存不受系统的管理,绑定到特定的设备,在设备需要使用的时候再对这部分内存进行管理,这就是内核中提供的 reserved memory 机制,这是内核中比较传统的内存预留机制。

在实际应用中,为特定设备预留内存虽然实现和操作都相对简单,但是有个明显的缺点:正因为这部分内存不能被系统管理,也就造成了一定的浪费,在设备不使用的时候,这部分内存就无法利用的,而最理想的情况应该是:当被预留的内存在不使用的时候同样可以被系统利用,而设备需要使用的时候就返回给其绑定的设备,这种预留内存的机制从实现上来说要相对复杂一些,但是大大地提高了内存利用率,这就是内核中的 CMA 机制。

本文将重点分析内核中的 reserved-memory,不仅仅是 dts 中配置的节点,还会补充内核中其他被 reserved 的内存。

1. DTS中的reserved memory配置

下图截取自我司项目,代号:C3F:

&reserved_memory {
	#address-cells = <2>;
	#size-cells = <2>;
	ranges;

	/* global autoconfigured region for contiguous allocations */
	system_cma: linux,cma {
		compatible = "shared-dma-pool";
		alloc-ranges = <0x0 0x00000000 0x0 0xffffffff>;
		reusable;
		alignment = <0x0 0x400000>;
		size = <0x0 0x2000000>;
		linux,cma-default;
	};

	ramoops_mem: ramoops_region {
		compatible = "ramoops";
		alloc-ranges = <0x0 0x00000000 0xffffffff 0xffffffff>;
		size = <0x0 0x200000>;
		pmsg-size = <0x200000>;
		mem-type = <2>;
	};
};

&reserved_memory {
	#address-cells = <2>;
	#size-cells = <2>;
	ranges;

	hyp_mem: hyp_region@80000000 {
		no-map;
		reg = <0x0 0x80000000 0x0 0x600000>;
	};

	xbl_dtlog_mem: xbl_dtlog_region@80600000 {
		no-map;
		reg = <0x0 0x80600000 0x0 0x40000>;
	};

	xbl_ramdump_mem: xbl_ramdump_region@80640000 {
		no-map;
		reg = <0x0 0x80640000 0x0 0x1c0000>;
	};
};

上面的设备树中正好对应着三种类型的reserved-memory:dma/普通reserved-memory/no-map类型的

1.1 根节点中属性

#address-cells#size-cells 在设备树中属于比较重要的概念,用于指定其同一级节点中描述一个单元的信息所需要的字节数。cell 值为1,表示使用4个bytes描述。cell 值为2,表使用8个bytes描述(高4 bytes和低 4 bytes),例如上面的 reg 属性。

这两个属性值需要使用相同的值。

ranges 属性也是根节点必须的。只要空定义即可。

1.2 静态预留和动态预留

在 reserved-memory 中节点分动态和静态两种方式。

静态预留: 在dts 中配置 reg 属性,指定reserved 的base 和 size;

动态预留: 在dts 中没有reg 属性,只指定了 size 。当然也可以加上 alignment 和 alloc-ranges 属性配合;

  • alignment: 对齐参数,预留内存的起始地址需要向该参数对齐,配置的值按照 #size-cells
  • alloc-ranges: 指定可以申请动态 reserved 内存的空间;没有指定,则从整个memory 中申请;

当同时配置 reg 和 size 属性时,优先选择 reg 属性配置。

注意:

对于 reserved-memory 子节点,想要将该节点配置为 reserved 节点,必须拥有 res 属性或 size 属性,否则系统将会在扫描的时候 pass 掉这个节点。

1.3 compatible

可选, 通常情况下,预留内存并不需要 compatible 属性,因为预留内存并不需要相应的驱动程序来处理它。

特殊的情况是 CMA,CMA内存在作为预留内存的同时,还需要被buddy 系统管理,需要做一些特殊的设置,因此需要编写相应的驱动程序, 使用 compatible 属性来关联相应的驱动程序,并在内核启动阶段调用,完成CMA预留内存的初始化。CMA 的compatible 属性值为 shared-dma-pool,这个是内核规定的一个固定值。

系统会通过 RESERVEDMEM_OF_DECLARE() 定义很多 reservedmem table。

调用一个 reserved mem 的宏定义,有三个参数,reserved_mem 的 namecompatible 和初始化的函数指针。

// 这个_OF_DECLAREH的宏函数会创建一个名为__#table_of_table的特殊的section
// 我们所跟踪的GICv3的初始化所用的宏为IRQCHIP_DECLARE,最终调用的也就是这个宏函数
#define _OF_DECLARE(table, name, compat, fn, fn_type)			\
	static const struct of_device_id __of_table_##name		\
		__used __section("__" #table "_of_table")		\
		__aligned(__alignof__(struct of_device_id))		\
		 = { .compatible = compat,				\
		     .data = (fn == (fn_type)NULL) ? fn : fn  }

#define RESERVEDMEM_OF_DECLARE(name, compat, init)			\
	_OF_DECLARE(reservedmem, name, compat, init, reservedmem_of_init_fn)

那上图定义的两个定义,展开后即为

static const struct of_device_id __of_table_dma
		__used __section(__reservedmem_of_table)
		__aligned(__alignof__(struct of_device_id))
		 = { .compatible = "shared-dma-pool",
		     .data = rmem_dma_setup  }

static const struct of_device_id __of_table_cma
		__used __section(__reservedmem_of_table)
		__aligned(__alignof__(struct of_device_id))
		 = { .compatible = "shared-dma-pool",
		     .data = rmem_cma_setup  }

该变量位于 __reservedmem_of_table 字段,从 System.map 文件中可以看到有多个 __XX_of_table section 的定义,它们都被位于 .init.data section 中。

后面会轮询这些 __of_table_xx,进而取出 .data 中的函数回调。对于这里会调用 rmem_cma_setup() 来初始化 CMA 内存。

1.4 no-map

可选,如果预留内存配置该属性,则表示这部分预留内存不会建立虚拟地址到物理地址的映射,也就是即使获取这部分预留内存,也是不能直接访问的,而是需要使用者自己建立页表映射,通常使用 ioremap 来重新建立映射再访问。为预留内存添加 no-map 属性可以提供更高的安全性和灵活性。

memblock.reserved中内存都依然存在于 memblock.memory 中,memblock.memory 中所有的物理内存都会建立虚拟映射。在内存初始化的后期阶段,memblock.memory 除去 memblock.reserved 中的所有内存页面,都会交给 buddy 系统管理。

设置了 no-map 属性的预留内存,会从 memblock.memory 中删除,就好像物理上没有提供这片内存一样,后续的内存管理自然不会管理这片内存。当然,对于 CMA 来说,使用了reusable 属性,被依然存放在 memblock.memory 中,也会被交给 buddy 系统,只是这片内存会被标记为 MIGRATE_CMA,表示这是 CMA 内存,用户只有在申请 MOVABLE 类型的内存时才能使用这部分内存。

1.5 reusable

可选,预留内存中如果配置该属性,则表示这部分内存可以被 buddy 系统利用,CMA 就属于这种,也可以自定义其他的预留内存重复利用。

对于一个预留内存节点,当配置了 no-map 属性,就不要使用 reusable 了,系统优先选择 no-map 属性。对于 CMA 节点来说,只能配置 reusable 属性,而不能配置 no-map 属性。

1.6 linux,cma-default

这个是针对 CMA 节点的,如果指定 linux , cma-default 属性,内核在分配 cma 内存时会将这片内存当成默认的 cma 分配池使用。

1.7 linux,dma-default

用作 dma 的预留内存,当指定了 linux,dma-default 属性,内核在分配 dma 内存时将会默认使用该内存当做 dma 分配池。

1.8 总结

属性解释
#address-cells和#size-cells用于指定其同一级节点中描述一个单元的信息所需要的字节数
reg静态预留: 在dts 中配置 reg 属性,指定reserved 的base 和 size
size动态预留: 在dts 中没有reg 属性,只指定了 size 。当然也可以加上 alignment 和 alloc-ranges 属性配合
alignment对齐参数,预留内存的起始地址需要向该参数对齐,配置的值按照 **#size-cells **
alloc-ranges指定可以申请动态 reserved 内存的空间;没有指定,则从整个memory 中申请
compatible使用 compatible 属性来关联相应的驱动程序,一般是CMA
no-map可选,如果预留内存配置该属性,则表示这部分预留内存不会建立虚拟地址到物理地址的映射
reusable可选,预留内存中如果配置该属性,则表示这部分内存可以被 buddy 系统利用,CMA 就属于这种,也可以自定义其他的预留内存重复利用。
linux,cma-default这个是针对 CMA 节点的,如果指定linux,cma-default 属性,内核在分配 cma 内存时会将这片内存当成默认的 cma 分配池使用
linux,dma-default用作 dma 的预留内存,当指定了linux, dma-default 属性,内核在分配 dma 内存时将会默认使用该内存当做 dma 分配池

2. reserved-memory 节点的解析

紧接上文[linux内存管理 第008章 memblock子系统详解],详细地说明了 memblock 的初始化过程,其中也包括了解析 reserved-memory 节点,其入口函数为 early_init_fdt_scan_reserved_mem():

void __init early_init_fdt_scan_reserved_mem(void)
{
	int n;
	u64 base, size;

        //fdt的虚拟地址,在 setup_machine_fdt() 中会将fdt的虚拟地址赋值给该变量
	if (!initial_boot_params)
		return;

	/* Process header /memreserve/ fields */
        //在dts中有些vendor会定义memreserve,address 和 size,会在这里读取并配置为reserved-memory
	for (n = 0; ; n++) {
		fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
		if (!size)
			break;
		early_init_dt_reserve_memory_arch(base, size, false);
	}
        //对dtb中每个节点进行__fdt_scan_reserved_mem(),进而找到reserved-memory节点
	of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);
        //对 reserved-memory 的子节点深入的解析
	fdt_init_reserved_mem();
        //elf core header内存的映射
	fdt_reserve_elfcorehdr();
}

2.1 __fdt_scan_reserved_mem

static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname,
					  int depth, void *data)
{
	static int found;
	int err;


        //确认是否是第一次找到 reserved-memory这个节点
	if (!found && depth == 1 && strcmp(uname, "reserved-memory") == 0) {
                //确认reserved-memory这个节点是否满足要求为根节点
                //需要配置三个属性:
                //  1) #size-cells
                //  2) #address-cells
                //  3) ranges
                //如果都配置了,则会返回0,否则返回errno
		if (__reserved_mem_check_root(node) != 0) {
			pr_err("Reserved memory: unsupported node format, ignoring\n");
			/* break scan */
			return 1; //如果配置出问题,返回1,表示将跳出对reserved-memory的扫描
		}
		found = 1;
		/* scan next node */
		return 0; //如果根节点配置ok,返回0,表示找到了reserved-memory节点,继续扫描子节点
	} else if (!found) {
		/* scan next node */
                //还没有找到跟节点 reserved-memory,返回0,继续扫描
		return 0;
	} else if (found && depth < 2) {
		/* scanning of /reserved-memory has been finished */
                //当到了reserved-memory的尾部了,depth会回到1,标记reserved-memory扫描完成
		return 1;
	}
         //遇到子节点中设置了status属性,则确认是否设置了ok或okay
        //没有设置status,或设置ok/okay,都认为是available的,返回true,否则为false
	if (!of_fdt_device_is_available(initial_boot_params, node))
		return 0;
        // 两种方式处理reserved-memory节点中的子节点
        //   1) 设置reg属性;
        //   2) 没有设置reg属性,但设置了size属性
	err = __reserved_mem_reserve_reg(node, uname);
	if (err == -ENOENT && of_get_flat_dt_prop(node, "size", NULL))
		fdt_reserved_mem_save_node(node, uname, 0, 0);

	/* scan next node */
	return 0;
}

这里主要是通过 of_scan_flat_dt() 函数回调 __fdt_scan_reserved_mem() 函数,用来扫描 reserved-memory 节点及其子节点。

该函数主要分两部分:

  • 是否找到了节点 reserved-memory?
    • 如果没有找到, of_scan_flat_dt() 会继续扫描;
    • 如果找到了 reserved-memory 节点,需要确定该节点配置 #size-cells#address-cellsranges 是否合适;
  • 扫描 reserved-memory 的子节点
    • 确认该子节点是否设置了 status 属性,确认是否设置了 ok/okay 的属性值;
    • 确认该子节点是否设置了 reg 属性;如果没有设置 reg,确认是否设置了 size 属性;

注意1:

对于没有配置 reg 属性,而配置了 size 属性的就会通过函数 fdt_reserved_mem_save_node() 先简单初始化时,会将 base 和 size 都设为0,主要是将信息占据 reserved_mem 数组一个元素,详细的节点初始化工作将会留在后面 fdt_init_reserved_mem() 函数中进行。

注意2:

如果 reserved-memory 的子节点没有配置 reg 属性或 size 属性,系统在扫描的时候会将其 pass,即认为该节点不是有效的 reserved 节点。

2.1.1 __reserved_mem_reserve_reg()

/*
 * __reserved_mem_reserve_reg() - reserve all memory described in 'reg' property
 */
static int __init __reserved_mem_reserve_reg(unsigned long node,
					     const char *uname)
{
	int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
	phys_addr_t base, size;
	int len;
	const __be32 *prop;
	int first = 1;
	bool nomap;

	prop = of_get_flat_dt_prop(node, "reg", &len);
	if (!prop)
		return -ENOENT;

	if (len && len % t_len != 0) {
		pr_err("Reserved memory: invalid reg property in '%s', skipping node.\n",
		       uname);
		return -EINVAL;
	}

	nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;

	while (len >= t_len) {
		base = dt_mem_next_cell(dt_root_addr_cells, &prop);
		size = dt_mem_next_cell(dt_root_size_cells, &prop);

		if (size &&
		    early_init_dt_reserve_memory_arch(base, size, nomap) == 0)
			pr_debug("Reserved memory: reserved region for node '%s': base %pa, size %lu MiB\n",
				uname, &base, (unsigned long)(size / SZ_1M));
		else
			pr_info("Reserved memory: failed to reserve memory for node '%s': base %pa, size %lu MiB\n",
				uname, &base, (unsigned long)(size / SZ_1M));

		len -= t_len;
		if (first) {
			fdt_reserved_mem_save_node(node, uname, base, size);
			first = 0;
		}
	}
	return 0;
}

该函数主要确认reserved-memory 子节点是否含有 reg 属性。

如果配置了 reg 属性,会通过该属性值去解析 addr 和 size,同时会确定是否配置了 no-map 属性值。如果设定了 no-map,则将该区域从 memblock.memory 去掉,但不会加入到 memblock.reserved 中;如果没有设定,则将该区域直接加到 memblock.reserved 中。

在函数的最后会调用 fdt_reserved_mem_save_node(),用以将该子节点信息保存到 reserved_mem 数组中。

下面看下 early_init_dt_reserve_memory_arch() 函数:

static int __init early_init_dt_reserve_memory_arch(phys_addr_t base,
					phys_addr_t size, bool nomap)
{
	if (nomap) {
		/*
		 * If the memory is already reserved (by another region), we
		 * should not allow it to be marked nomap.
		 */
		if (memblock_is_region_reserved(base, size))
			return -EBUSY;

		return memblock_mark_nomap(base, size);
	}
	return memblock_reserve(base, size);
}

如果配置了 no-map 属性,则将该内存从 memblock.memory 中移除,不会加入到内存管理中。

如果没有配置 no-map 属性,则认为该内存是可以映射的 reserved, 不会从 memblock.memory 中移除,只是在后期不会加入到 buddy 管理中。

2.1.2 fdt_reserved_mem_save_node()

void __init fdt_reserved_mem_save_node(unsigned long node, const char *uname,
				      phys_addr_t base, phys_addr_t size)
{
	struct reserved_mem *rmem = &reserved_mem[reserved_mem_count];

	if (reserved_mem_count == ARRAY_SIZE(reserved_mem)) {
		pr_err("not enough space for all defined regions.\n");
		return;
	}

	rmem->fdt_node = node;
	rmem->name = uname;
	rmem->base = base;
	rmem->size = size;

	reserved_mem_count++;
	return;
}

无论是该 reserved-memory 的子节点是配置了 reg 属性还是 size 属性,最终都会调用到该函数, 其根本目的就是为了将节点存在 reserved_mem 数组中,标记该子节点初步成为 reserved 一员,后面在fdt_inti_reserved_mem() 将按照size 或 reg 分配内存,将其成为正式 reserved memory。

  • 当配置了 reg 属性,会在 __reserved_mem_reserve_reg() 函数的最后调用该函数,将节点信息保存到 reserved_mem 数组中;
  • 当没有配置 reg 属性时,__fdt_scan_reserved_mem() 函数最后调用该函数,只不过此时会将 base 和 size 都设为0,在 fdt_init_reserved_mem() 函数中在进行详细的初始化。

继续来看 reserved_mem 数组:


drivers/of/of_reserved_mem.c
 
#define MAX_RESERVED_REGIONS	64
static struct reserved_mem reserved_mem[MAX_RESERVED_REGIONS];
static int reserved_mem_count;

include/linux/of_reserved_mem.h
 
struct reserved_mem {
	const char			*name;
	unsigned long			fdt_node;
	unsigned long			phandle;
	const struct reserved_mem_ops	*ops;
	phys_addr_t			base;
	phys_addr_t			size;
	void				*priv;
};

注意:

按照上述代码来看,Linux 系统要求 dtb 中 reserved-memory 的节点最多允许配置 64 个。

2.2 fdt_init_reserved_mem

void __init fdt_init_reserved_mem(void)
{
	int i;

	/* check for overlapping reserved regions */
	__rmem_check_for_overlap();

	for (i = 0; i < reserved_mem_count; i++) {
		struct reserved_mem *rmem = &reserved_mem[i];
		unsigned long node = rmem->fdt_node;
		int len;
		const __be32 *prop;
		int err = 0;
		bool nomap;

		nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
		prop = of_get_flat_dt_prop(node, "phandle", &len);
		if (!prop)
			prop = of_get_flat_dt_prop(node, "linux,phandle", &len);
		if (prop)
			rmem->phandle = of_read_number(prop, len/4);
                 //注意这里,size为0的情况:
                 //   1) 节点中没有配置reg属性  ---- 需要继续确认size 属性值
                 //   2) 没有设置reg,而是设置了size属性  ----此时初始值都为0
                //当通过size属性时,还需要同时配置alloc_ranges属性,用以从memblock中查找range
		if (rmem->size == 0)
			err = __reserved_mem_alloc_size(node, rmem->name,
						 &rmem->base, &rmem->size);
		if (err == 0) {
			err = __reserved_mem_init_node(rmem);
			if (err != 0 && err != -ENOENT) {
				pr_info("node %s compatible matching fail\n",
					rmem->name);
				if (nomap)
					memblock_clear_nomap(rmem->base, rmem->size);
				else
					memblock_free(rmem->base, rmem->size);
			}
		}
	}
}

详细的处理流程,请看上述代码。

函数两个重要的注意点:

  • rmem->size 为 0 时,是该节点配置了 size 属性,调用 __reserved_mem_alloc_size() 确定最终的 size 值;
  • 当 size 值确认后,调用 __reserved_mem_init_node() 对放入 __reservedmem_of_table 段的特殊reserved 节点进行最后初始化工作;

下面详细来剖析下这两个重要函数。

2.2.1 __reserved_mem_alloc_size

/*
 * __reserved_mem_alloc_size() - allocate reserved memory described by
 *	'size', 'alignment'  and 'alloc-ranges' properties.
 */
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;
	bool 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);

	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);
	}

	nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;

	/* 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)
	    && !nomap) {
		unsigned long order =
			max_t(unsigned long, MAX_ORDER - 1, pageblock_order);

		align = max(align, (phys_addr_t)PAGE_SIZE << order);
	}

	prop = of_get_flat_dt_prop(node, "alloc-ranges", &len);
	if (prop) {

		if (len % t_len != 0) {
			pr_err("invalid alloc-ranges property in '%s', skipping node.\n",
			       uname);
			return -EINVAL;
		}

		base = 0;

		while (len > 0) {
			start = dt_mem_next_cell(dt_root_addr_cells, &prop);
			end = start + dt_mem_next_cell(dt_root_size_cells,
						       &prop);

			ret = early_init_dt_alloc_reserved_memory_arch(size,
					align, start, end, nomap, &base);
			if (ret == 0) {
				pr_debug("allocated memory for '%s' node: base %pa, size %lu MiB\n",
					uname, &base,
					(unsigned long)(size / SZ_1M));
				break;
			}
			len -= t_len;
		}

	} else {
		ret = early_init_dt_alloc_reserved_memory_arch(size, align,
							0, 0, nomap, &base);
		if (ret == 0)
			pr_debug("allocated memory for '%s' node: base %pa, size %lu MiB\n",
				uname, &base, (unsigned long)(size / SZ_1M));
	}

	if (base == 0) {
		pr_info("failed to allocate memory for node '%s'\n", uname);
		return -ENOMEM;
	}

	*res_base = base;
	*res_size = size;

	return 0;
}

详细看上面代码中的注释,除了确认子节点是否配置了size、no-map、alignment,最主要是确定 alloc-ranges 属性值,从该range 中选定memblock 的区域。

如果没有配置 alloc-ranges属性值,则从整个memblock 中分配。

当确定好分配的base 和 size之后,通过函数 early_init_dt_alloc_reserved_memory_arch() 分配:

static int __init early_init_dt_alloc_reserved_memory_arch(phys_addr_t size,
	phys_addr_t align, phys_addr_t start, phys_addr_t end, bool nomap,
	phys_addr_t *res_base)
{
	phys_addr_t base;
	int err = 0;

	end = !end ? MEMBLOCK_ALLOC_ANYWHERE : end;
	align = !align ? SMP_CACHE_BYTES : align;
	base = memblock_phys_alloc_range(size, align, start, end);
	if (!base)
		return -ENOMEM;

	*res_base = base;
	if (nomap) {
		err = memblock_mark_nomap(base, size);
		if (err)
			memblock_free(base, size);
		kmemleak_ignore_phys(base);
	}

	return err;
}

从区域分配出memblock 内存,如果获取到的 base 有效,则判断是否设置了 no-map 属性,从而确定该区域是从 memblock . memory 中 remove,还是将其防置到 memblock . reserved 中。

**注意:**当没有配置 alloc-ranges 属性时,入参 start、end 都为0。

2.2.2 __reserved_mem_init_node()

参数:

rmem: 全局数据变量 reserved_mem 中某个元素的地址;

static int __init __reserved_mem_init_node(struct reserved_mem *rmem)
{
	extern const struct of_device_id __reservedmem_of_table[];
	const struct of_device_id *i;
	int ret = -ENOENT;

	for (i = __reservedmem_of_table; i < &__rmem_of_table_sentinel; i++) {
		reservedmem_of_init_fn initfn = i->data;
		const char *compat = i->compatible;

		if (!of_flat_dt_is_compatible(rmem->fdt_node, compat))
			continue;

		ret = initfn(rmem);
		if (ret == 0) {
			pr_info("initialized node %s, compatible id %s\n",
				rmem->name, compat);
			break;
		}
	}
	return ret;
}

轮询 __reservedmem_of_table section 中所有结构体变量,确认当前的节点是否满足结构体变量中的 compatible,如果满足则会调用初始化这个 table 时的函数指针(即struct of_device_id 的data 成员)。

System.map

ffffffc00a082068 d __of_table_cma
ffffffc00a082068 D __reservedmem_of_table
ffffffc00a082130 d __of_table_dma
ffffffc00a0821f8 d __rmem_of_table_sentinel

__of_table_cma 等于 __reservedmem_of_table,也就是说 __of_table_cma 是__reservedmem_of_table 数组的第一个元素,这里满足条件的只有两个特殊节点。

{% tip success %}

对于 CMA 初始化,本篇中不做分析,待后续CMA分配器详解阶段一起分析

{% endtip %}

3. 小结

通过上面两节,详细地剖析了 dtsi 中 reserved-memory 节点的属性意义,也剖析了内核中对于该节点的解析过程。这里做个小结:

  • setup_arch() 函数中,fixmap 初始化完之后,会调用 setup_machine_fdt() 函数对 dtb 进行映射。接着调用 arm64_memblock_init() 函数对 memblock 进行初始化,其中 reserved-memory 解析会在 early_init_fdt_scan_reserved_mem() 函数中处理。
  • 首先会找到 dts 中根节点 reserved-memory,并对其主要的 #address-cells、#size-cells、ranges 属性进行确认;
  • 解析子节点,确认是静态预留还是动态预留,并通过 size、alloc-ranges 等属性确认预留内存的最终 base、size 值;
  • memblock . reserved 内存都会存在于 memblock . memory 中,memblock.memory 中所有的物理内存都会建立虚拟映射。在内存初始化的后期阶段,memblock . memory 除去 memblock . reserved 中的所有内存页面,都会交给 buddy 系统管理;
  • 设置 no-map 的预留内存,将会从 memblock . memory 中移除,即后期不会交由内存管理;
  • no-map 属性和 reusable 属性时不能同时设定的;
  • 预留内存中 CMA 是个例外,dts 中会配置 compatible 属性,表示这是一个特殊的预留内存,它会有初始化的动作,通过 __of_table_xx 指定初始化函数,在确定好预留内存 base、size 之后系统会调用所有指定定义了 _of_table_xx 中指定了 compatible的预留内存所指定的初始化函数,以此来初始化这一段特殊的预留内存;
  • 另外,CMA 还配置了 reusable 属性,而不能配置 no-map,否则无效;