内核对设备树的处理(二)__kernel对dts节点的处理

我们知道设备树只是起到信息传递的作用,所以对配置信息的处理还是相对比较简单的,
设备树只不过从dtb文件中把信息给提取出来付给内核中的某个变量就可以了,

  1. 下面这条命令,就是内核启动时的命令行参数:
	chosen {
		bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
	};

root=/dev/mtdblock4:指定了跟文件系统在哪里;
init=/linuxrc:指定第一个运行的应用程序是哪一个;
console=ttySAC0:指定内核的打印信息从哪个设备打印出来;

  1. memory节点用来描述硬件内存布局的。如果有多块内存,既可以通过多个memory节点表示,也可以通过一个memory节点的reg属性的多个元素支持。不同的板子内存的起始地址和大小可能不一样,我们需要在设备树中把内存的起始地址和大小告诉内核。
	memory@30000000 {
		device_type = "memory";
		reg =  <0x30000000 0x4000000>;
	};
#address-cells = <1>;  //1个32bit 表示内存的起始地址
#size-cells = <1>;     //1个32bit 表示内存的大小

内存起始地址和大小用多少个32bit表示,通过设置#address-cells和#size-cells来指定,譬如:

#address-cells = <n>;  //n个32bit 表示内存的起始地址
#size-cells = <m>     //个32bit 表示内存的大小
memory@0 {
        device_type = "memory";
        reg = <0x000000000 0x00000000 0x00000000 0x80000000
               0x000000001 0x00000000 0x00000001 0x00000000>;
    };

两个memory节点的形式如下:
    memory@0 {
        device_type = "memory";
        reg = <0x000000000 0x00000000 0x00000000 0x80000000>;
    };
    memory@100000000 {
        device_type = "memory";
        reg = <0x000000001 0x00000000 0x00000001 0x00000000>;
    };

所以在解析memory@30000000节点之前需要解析#address-cells和#size-cells,

下面分析设备树文件节点信息是怎样传给内核的:

  1. /chosen节点中bootargs属性的值, (怎么提取出来)怎么存入全局变量得?
    解析:在之前提起过,head.S会把DTB的位置保存在变量__atags_pointer里,最后调用start_kernel。

head.S中的部分内容

   str	r9, [r0]			@ Save processor ID
	str	r7, [r1]			@ Save machine type
	str	r8, [r2]			@ Save atags pointer
	cmp	r3, #0
	strne	r10, [r3]			@ Save control register values
	mov	lr, #0
	b	start_kernel

start_kernel函数调用过程:

start_kernel // init/main.c
	 setup_arch(&command_line);  // arch/arm/kernel/setup.c
		/* 使用dtb文件设置machine */
		mdesc = setup_machine_fdt(__atags_pointer); // arch/arm/kernel/setup.
			mdesc_best = &__mach_desc_GENERIC_DT; //选择最合适的mach_desc
			early_init_dt_scan_nodes();  //早期的初始化扫描节点
				/* Retrieve various information from the /chosen node */
				of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

				/* Initialize {size,address}-cells info */
				of_scan_flat_dt(early_init_dt_scan_root, NULL);

				/* Setup memory, calling early_init_dt_add_memory_arch */
				of_scan_flat_dt(early_init_dt_scan_memory, NULL);

主要分析early_init_dt_scan_nodes内的函数

void __init early_init_dt_scan_nodes(void)
{
	/* 扫描 /chosen node,保存运行时参数(bootargs)到boot_command_line */
	of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

	/* 扫描根节点,获取 {size,address}-cells信息,并保存在dt_root_size_cells和dt_root_addr_cells全局变量中 */
	of_scan_flat_dt(early_init_dt_scan_root, NULL);

	/* 扫描DTB中的memory node,并把相关信息保存在meminfo中,全局变量meminfo保存了系统内存相关的信息 */
	of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}

of_scan_flat_dt函数扫描整个设备树,实际的动作是在回调函数中完成的。
第4行是对chosen节点操作,该行代码的作用是将节点下的bootargs属性的字符串拷贝到boot_command_line指向的内存中。boot_command_line是内核的一个全局变量,在内核的多处都会用到。
第8行是根据根节点的#address-cells属性和#size-cells属性初始化全局变量dt_root_size_cells和dt_root_addr_cells,如果没有设置属性的话就用默认值,这些都在early_init_dt_scan_root函数中实现。
第12行是对内存进行初始化。

/**
 * of_scan_flat_dt - scan flattened tree blob and call callback on each.
 * @it: callback function
 * @data: context data pointer
 *
 * This function is used to scan the flattened device-tree, it is
 * used to extract the memory information at boot before we can
 * unflatten the tree
 */
int __init of_scan_flat_dt(int (*it)(unsigned long node,
				     const char *uname, int depth,
				     void *data),
			   void *data)
{
	const void *blob = initial_boot_params;
	const char *pathp;
	int offset, rc = 0, depth = -1;

	if (!blob)
		return 0;

	/* 取出设备树文件中的每一个节点 */
	for (offset = fdt_next_node(blob, -1, &depth);
	     offset >= 0 && depth >= 0 && !rc;
	     offset = fdt_next_node(blob, offset, &depth)) {

		pathp = fdt_get_name(blob, offset, NULL);
		if (*pathp == '/')
			pathp = kbasename(pathp);
		rc = it(offset, pathp, depth, data);  //调用该函数来处理,rc值为非0值时
	}                                         //表示处理完成
	return rc;
}

从函数的注释我们可以看到it参数是传入的函数指针,data是给it参数指向的函数
使用的, 函数的作用是取出每一个节点,然后调用it指向的函数来处理,如果it返回的值
不为0的话就退出这个循环(返回非0值表示处理完)。对于这个函数我们主要关心it参数指向的函数就可以了。

int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
				     int depth, void *data)
{
	int l;
	const char *p;

	pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);

	if (depth != 1 || !data ||
	    (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
		return 0;  ------------------------------------<1>

	early_init_dt_check_for_initrd(node);------------------------------------<2>

	/* Retrieve command line */
	p = of_get_flat_dt_prop(node, "bootargs", &l);------------------------------------<3>
	if (p != NULL && l > 0)
		strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE));

	/*
	 * CONFIG_CMDLINE is meant to be a default in case nothing else
	 * managed to set the command line, unless CONFIG_CMDLINE_FORCE
	 * is set in which case we override whatever was found earlier.
	 */
#ifdef CONFIG_CMDLINE
#if defined(CONFIG_CMDLINE_EXTEND)
	strlcat(data, " ", COMMAND_LINE_SIZE);
	strlcat(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#elif defined(CONFIG_CMDLINE_FORCE)
	strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#else
	/* No arguments from boot loader, use kernel's  cmdl*/
	if (!((char *)data)[0])
		strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#endif
#endif /* CONFIG_CMDLINE */

	pr_debug("Command line is: %s\n", (char*)data);

	/* break now */
	return 1;   ------------------------------------<4>
}

<1>、上面我们说过,early_init_dt_scan_chosen会为设备树中的每一个节点而调用一次,因此,为了效率,不是选择节点的节点我们必须赶紧闪人。由于所选节点是根节点的子节点,因此其深度必须是1。这里深度不是1的节点,节点名字不是“选择”或者选择@ 0和我们毫无关系,立刻返回。

<2>、解析选择节点中的initrd的信息

<3>、解析选择节点中的bootargs(命令行参数)并将其复制到boot_command_line。

<4>、除非设置了CONFIG_CMDLINE_FORCE,否则CONFIG_CMDLINE意味着是默认值,除非设置了CONFIG_CMDLINE_FORCE,否则我们会覆盖之前找到的内容。

/**
 * early_init_dt_scan_root - fetch the top level address and size cells
 */
int __init early_init_dt_scan_root(unsigned long node, const char *uname,
				   int depth, void *data)
{
	const __be32 *prop;

	if (depth != 0)
		return 0;

	dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
	dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
	------------------------------------<1>

	prop = of_get_flat_dt_prop(node, "#size-cells", NULL);     --------------------<2>
	if (prop)
		dt_root_size_cells = be32_to_cpup(prop);
	pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells);

	prop = of_get_flat_dt_prop(node, "#address-cells", NULL); --------------------<3>
	if (prop)
		dt_root_addr_cells = be32_to_cpup(prop);
	pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells);

	/* break now */
	return 1;
}

<1>、dt_root_size_cells和dt_root_addr_cells分别用来保存和设备树文件中#address-cells和#size-cells的值,刚开始设置默认值都是1。

<2>、获得"#size-cells"属性的值(获取到的格式是大段格式),通过函数be32_to_cpup处理转化为小段模式赋值给dt_root_size_cells全局变量。

<3>、获得"#address-cells"属性的值(获取到的格式是大段格式),通过函数be32_to_cpup处理转化为小段模式赋值给dt_root_addr_cells全局变量。

/**
 * early_init_dt_scan_memory - Look for and parse memory nodes
 */
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
				     int depth, void *data)
{
	const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
	const __be32 *reg, *endp;
	int l;
	bool hotpluggable;

	/* We are scanning "memory" nodes only */
	if (type == NULL || strcmp(type, "memory") != 0)
		return 0; --------------------------<1>
	reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
	if (reg == NULL)
		reg = of_get_flat_dt_prop(node, "reg", &l);
	if (reg == NULL)
		return 0;----------------------<2>

	endp = reg + (l / sizeof(__be32));
	hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL);

	pr_debug("memory scan node %s, reg size %d,\n", uname, l);

	while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
		u64 base, size;

		base = dt_mem_next_cell(dt_root_addr_cells, &reg);
		size = dt_mem_next_cell(dt_root_size_cells, &reg); ----------------------<3>

		if (size == 0)
			continue;
		pr_debug(" - %llx ,  %llx\n", (unsigned long long)base,
		    (unsigned long long)size);

		early_init_dt_add_memory_arch(base, size);

		if (!hotpluggable)
			continue;

		if (early_init_dt_mark_hotplug_memory_arch(base, size))
			pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n",
				base, base + size);
	}

	return 0;
}

<1>、如果该memory node是root node的子节点的话,那么它一定是有device_type属性并且其值是字符串”memory”。不是的话就可以返回了。

<2>、该memory node的物理地址信息保存在"linux,usable-memory"或者"reg"属性中(reg是我们常用的)。

<3>、解析/memory中的reg属性, 提取出"base, size", 最终调用memblock_add(base, size);

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值