设备树(二):dtb展开为device_node

kernel版本:4.4.143
内核文档usage-model.txt对设备树的3个用途做了总结:

  1. platform identification //平台鉴别
  2. runtime configuration //运行时配置
  3. device population //设备填充

1. platform identification处理

--> start_kernel(void)  //kernel\init\Main.c
----> setup_arch(&command_line) \\kernel\arch\arm\kernel\Setup.c
------> mdesc = setup_machine_fdt(__atags_pointer) //__atags_pointer为bootloader传递给内核的dtb在内存中的物理地址
--------> early_init_dt_verify(phys_to_virt(__atags_pointer)
----------> initial_boot_params = __atags_pointer;  //initial_boot_params为全局变量,将会被接下来用到
--------> mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); //找到设备树中与内核代码中定义相匹配的machine_desc,
                //逐个取出内核特定代码段中的machine_desc,然后逐个取出machine_desc.dt_compat中的元素和设备树根结点的compatible
                //各个值比较一遍,一但某个machine_desc.dt_compat中的元素和设备树根结点的compatible的某个值匹配,
                //mdesc就等于这个匹配上的machine_desc
内核所有machine_desc都放在.arch.info.init代码段中
例如内核(arch/arm/mach-rockchip/rockchip.c)中定义了
 DT_MACHINE_START(ROCKCHIP_DT, "Rockchip (Device Tree)")
         .l2c_aux_val    = 0,
         .l2c_aux_mask   = ~0,
         .init_time      = rockchip_timer_init,
         .dt_compat      = rockchip_board_dt_compat,
         .init_machine   = rockchip_dt_init,
 MACHINE_END
 展开宏为
	static const struct machine_desc __mach_desc_ROCKCHIP_DT  __used __attribute__((__section__(".arch.info.init"))) = {
		.nr             = ~0,
		.name           =  "Rockchip (Device Tree)",
	     .init_time      = rockchip_timer_init,
	     .dt_compat      = rockchip_board_dt_compat,
	     .init_machine   = rockchip_dt_init,
	  };
	  
	  static const char * const rockchip_board_dt_compat[] = {
		        "rockchip,rk2928",
		        "rockchip,rk3066a",
		        "rockchip,rk3066b",
		        "rockchip,rk3188",
		        "rockchip,rk3288",
		        NULL,
		};
最终rockchip_board_dt_compat中的 "rockchip,rk3288"会和compatible = "firefly,firefly-rk3288", "rockchip,rk3288";匹配

2.runtime configuration处理

--------> early_init_dt_scan_nodes()setup_machine_fdt(unsigned int dt_phys)中处理完machine_desc信息,
紧接着会通过early_init_dt_scan_nodes()函数处理platform identification信息,
platform identification信息为chosen节点下bootargs、根结点下#size-cells和#address-cells、
memory节点信息,memory节点下有device_type = "memory"才是有效的memory节点
		void __init early_init_dt_scan_nodes(void)
		{
			/* Retrieve various information from the /chosen node */
			of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line); //解析设备树chosen(chosen@0)节点下的bootargs属性,并赋值给全局变量boot_command_line
		
			/* Initialize {size,address}-cells info */
			of_scan_flat_dt(early_init_dt_scan_root, NULL); //解析设备树根节点#size-cells和#address-cells,并分别赋值给全局变量dt_root_size_cells 和dt_root_addr_cells
		
			/* Setup memory, calling early_init_dt_add_memory_arch */
			of_scan_flat_dt(early_init_dt_scan_memory, NULL); //解析设备树中有属性为device_type = "memory"的节点,读取内存信息,最终会向系统添加一个内存块
		}
		-->  early_init_dt_scan_memory(unsigned long node, const char *uname, int depth, void *data)
		----> early_init_dt_add_memory_arch(u64 base, u64 size)
		------> memblock_add(phys_addr_t base, phys_addr_t size) //添加一个内存块
		

3.device population处理

------> unflatten_device_tree();
--------> __unflatten_device_tree(initial_boot_params, &of_root, early_init_dt_alloc_memory_arch); //initial_boot_params指向uboot传过来的dtb地址,of_root将会指向由dtb展开的device_node根结点

                   //__unflatten_device_tree 中两次调用unflatten_dt_node
----------> size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true);  //计算所有将展开的device_node所需内存空间大小
----------> unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false); //mynodes(of_root)指向展开的device_node根节点

//下面以解析设备树为例剖析unflatten_dt_node函数
static void * unflatten_dt_node(const void *blob,
				void *mem,
				int *poffset,
				struct device_node *dad,
				struct device_node **nodepp,
				unsigned long fpsize,
				bool dryrun)
{
	const __be32 *p;
	struct device_node *np;
	struct property *pp, **prev_pp = NULL;
	const char *pathp;
	unsigned int l, allocl;
	static int depth;
	int old_depth;
	int offset;
	int has_name = 0;
	int new_format = 0;

	pathp = fdt_get_name(blob, *poffset, &l);//得到device_node 名字和名字长度
	if (!pathp)
		return mem;

	allocl = ++l;

	/* version 0x10 has a more compact unit name here instead of the full
	 * path. we accumulate the full path size using "fpsize", we'll rebuild
	 * it later. We detect this because the first character of the name is
	 * not '/'.
	 */
	if ((*pathp) != '/') {
		new_format = 1;
		if (fpsize == 0) {
			/* root node: special case. fpsize accounts for path
			 * plus terminating zero. root node only has '/', so
			 * fpsize should be 2, but we want to avoid the first
			 * level nodes to have two '/' so we use fpsize 1 here
			 */
			fpsize = 1;
			allocl = 2;
			l = 1;
			pathp = "";
		} else {
			/* account for '/' and path size minus terminal 0
			 * already in 'l'
			 */
			fpsize += l;
			allocl = fpsize;
		}
	}

	np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl, //申请一个device_node 结构体+节点名字长度的内存空间
				__alignof__(struct device_node));
	if (!dryrun) {
		char *fn;
		of_node_init(np);
		np->full_name = fn = ((char *)np) + sizeof(*np); //device_node的full_name指向节点结构体尾部
		if (new_format) {
			/* rebuild full path for new format */
			if (dad && dad->parent) {     //如果是根device_node 的子device_node的子device_node及以下device_node,该device_node full_name需要加上父device_node 名字
				strcpy(fn, dad->full_name);
#ifdef DEBUG
				if ((strlen(fn) + l + 1) != allocl) {
					pr_debug("%s: p: %d, l: %d, a: %d\n",
						pathp, (int)strlen(fn),
						l, allocl);
				}
#endif
				fn += strlen(fn);
			}
			*(fn++) = '/'; 
		}
		memcpy(fn, pathp, l); //加上该device_node 名称,等于从根device_node 起的全名

		prev_pp = &np->properties;
		if (dad != NULL) {
			np->parent = dad;
			np->sibling = dad->child;
			dad->child = np;
		}
	}
	/* process properties */
	for (offset = fdt_first_property_offset(blob, *poffset);
	     (offset >= 0);
	     (offset = fdt_next_property_offset(blob, offset))) { //解析device_node下所有property,并填充为property结构体链表
		const char *pname;
		u32 sz;

		if (!(p = fdt_getprop_by_offset(blob, offset, &pname, &sz))) {
			offset = -FDT_ERR_INTERNAL;
			break;
		}

		if (pname == NULL) {
			pr_info("Can't find property name in list !\n");
			break;
		}
		if (strcmp(pname, "name") == 0)
			has_name = 1;
		pp = unflatten_dt_alloc(&mem, sizeof(struct property),
					__alignof__(struct property));
		if (!dryrun) {
			/* We accept flattened tree phandles either in
			 * ePAPR-style "phandle" properties, or the
			 * legacy "linux,phandle" properties.  If both
			 * appear and have different values, things
			 * will get weird.  Don't do that. */
			if ((strcmp(pname, "phandle") == 0) ||
			    (strcmp(pname, "linux,phandle") == 0)) {
				if (np->phandle == 0)
					np->phandle = be32_to_cpup(p);
			}
			/* And we process the "ibm,phandle" property
			 * used in pSeries dynamic device tree
			 * stuff */
			if (strcmp(pname, "ibm,phandle") == 0)
				np->phandle = be32_to_cpup(p);
			pp->name = (char *)pname;
			pp->length = sz;
			pp->value = (__be32 *)p;
			*prev_pp = pp;
			prev_pp = &pp->next;
		}
	}
	/* with version 0x10 we may not have the name property, recreate
	 * it here from the unit name if absent
	 */
	if (!has_name) { //如果device_node没有name属性,则补充,补充内容为device_node名称@符号前的内容
		const char *p1 = pathp, *ps = pathp, *pa = NULL;
		int sz;

		while (*p1) {
			if ((*p1) == '@')
				pa = p1;
			if ((*p1) == '/')
				ps = p1 + 1;
			p1++;
		}
		if (pa < ps)
			pa = p1;
		sz = (pa - ps) + 1;
		pp = unflatten_dt_alloc(&mem, sizeof(struct property) + sz,
					__alignof__(struct property));
		if (!dryrun) {
			pp->name = "name";
			pp->length = sz;
			pp->value = pp + 1;
			*prev_pp = pp;
			prev_pp = &pp->next;
			memcpy(pp->value, ps, sz - 1);
			((char *)pp->value)[sz - 1] = 0;
			pr_debug("fixed up name for %s -> %s\n", pathp,
				(char *)pp->value);
		}
	}
	if (!dryrun) { //如果device_node 有name和device_type属性,则填充之
		*prev_pp = NULL;
		np->name = of_get_property(np, "name", NULL);
		np->type = of_get_property(np, "device_type", NULL);

		if (!np->name)
			np->name = "<NULL>";
		if (!np->type)
			np->type = "<NULL>";
	}

	old_depth = depth;
	*poffset = fdt_next_node(blob, *poffset, &depth);
	if (depth < 0)
		depth = 0;
	while (*poffset > 0 && depth > old_depth)
		mem = unflatten_dt_node(blob, mem, poffset, np, NULL,
					fpsize, dryrun); //递归调用解析完所有device_node

	if (*poffset < 0 && *poffset != -FDT_ERR_NOTFOUND)
		pr_err("unflatten: error %d processing FDT\n", *poffset);

	/*
	 * Reverse the child list. Some drivers assumes node order matches .dts
	 * node order
	 */
	if (!dryrun && np->child) {
		struct device_node *child = np->child;
		np->child = NULL;
		while (child) {
			struct device_node *next = child->sibling;
			child->sibling = np->child;
			np->child = child;
			child = next;
		}
	}

	if (nodepp)
		*nodepp = np;

	return mem;
}
 //下面看一下device_node和property两个结构体
				struct device_node {
					const char *name; //设备树节点存在name属性,则等于name值,否则等于设备树节点名@符号前面的内容
					const char *type; //等于属性名为device_type的值,不存在device_type等于"<NULL>"
					phandle phandle;
					const char *full_name; //从根结点开始的全名
					struct fwnode_handle fwnode;
				
					struct	property *properties;
					struct	property *deadprops;	/* removed properties */
					struct	device_node *parent;
					struct	device_node *child;
					struct	device_node *sibling; //兄弟device_node
					struct	kobject kobj;
					unsigned long _flags;
					void	*data;
				#if defined(CONFIG_SPARC)
					const char *path_component_name;
					unsigned int unique_id;
					struct of_irq_controller *irq_trans;
				#endif
				};		
				struct property {
					char	*name; //属性名称
					int	length;  //属性值的长度
					void	*value; //属性的值
					struct property *next; //下一个属性
					unsigned long _flags;
					unsigned int unique_id;
					struct bin_attribute attr;
				};
--------> of_alias_scan(early_init_dt_alloc_memory_arch);
void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
{
	struct property *pp;

	of_aliases = of_find_node_by_path("/aliases");
	of_chosen = of_find_node_by_path("/chosen");
	if (of_chosen == NULL)
		of_chosen = of_find_node_by_path("/chosen@0");

	if (of_chosen) {
		/* linux,stdout-path and /aliases/stdout are for legacy compatibility */
		const char *name = of_get_property(of_chosen, "stdout-path", NULL);
		if (!name)
			name = of_get_property(of_chosen, "linux,stdout-path", NULL);
		if (IS_ENABLED(CONFIG_PPC) && !name)
			name = of_get_property(of_aliases, "stdout", NULL);
		if (name)
			of_stdout = of_find_node_opts_by_path(name, &of_stdout_options); //of_stdout指向/chosen节点下*stdout*属性
			                                                           //所代表的标准输出,of_stdout_options等于/chosen
		                                                             //节点下*stdout*属性表明的参数,
		                                                            //例如chosen { stdout-path = "serial2:115200n8"; };
                                                                 //of_stdout指向serial2节点,of_stdout_options="115200n8"
	}

	if (!of_aliases)
		return;

	for_each_property_of_node(of_aliases, pp) { //解析of_aliases节点下所有属性
		const char *start = pp->name;
		const char *end = start + strlen(start);
		struct device_node *np;
		struct alias_prop *ap;
		int id, len;

		/* Skip those we do not want to proceed */
		if (!strcmp(pp->name, "name") ||
		    !strcmp(pp->name, "phandle") ||
		    !strcmp(pp->name, "linux,phandle"))
			continue;

		np = of_find_node_by_path(pp->value); //获取pp->value对应的device_node
		if (!np)
			continue;

		/* walk the alias backwards to extract the id and work out
		 * the 'stem' string */
		while (isdigit(*(end-1)) && end > start) //例如属性名字为“i2c0”,start指向'i',end指向'0'
			end--;
		len = end - start; //等于"i2c"长度

		if (kstrtoint(end, 10, &id) < 0)//把'0'转换成十进制的数字,赋值给id
			continue;

		/* Allocate an alias_prop with enough space for the stem */
		ap = dt_alloc(sizeof(*ap) + len + 1, 4);
		if (!ap)
			continue;
		memset(ap, 0, sizeof(*ap) + len + 1);
		ap->alias = start;
		of_alias_add(ap, np, id, start, len);
	}
	---------->  of_alias_add
	static void of_alias_add(struct alias_prop *ap, struct device_node *np,
			 int id, const char *stem, int stem_len)
{
	ap->np = np;
	ap->id = id;
	strncpy(ap->stem, stem, stem_len);
	ap->stem[stem_len] = 0;
	list_add_tail(&ap->link, &aliases_lookup); //把ap放入aliases_lookup链表
	pr_debug("adding DT alias:%s: stem=%s id=%i node=%s\n",
		 ap->alias, ap->stem, ap->id, of_node_full_name(np));
}
}
struct alias_prop {
	struct list_head link;
	const char *alias; //指向对应property的开头地址
	struct device_node *np; //指向代表i2c0的device_node
	int id; //等于0
	char stem[0]; //等于"i2c"
};
 aliases { //截取dts中的aliases节点
         i2c0 = &i2c0;
         i2c1 = &i2c1;
 };

4.aliases_lookup的使用

以i2c举例
kernel\drivers\i2c\busses\I2c-rk3x.c
static int rk3x_i2c_probe(struct platform_device *pdev)
{
	......
	int bus_nr;
	......
	/* Try to set the I2C adapter number from dt */
	bus_nr = of_alias_get_id(np, "i2c");
	......
}
在i2c-core.c里注册i2c adapter
int i2c_add_adapter(struct i2c_adapter *adapter)
{
	struct device *dev = &adapter->dev;
	int id;

	if (dev->of_node) {
		id = of_alias_get_id(dev->of_node, "i2c");
		if (id >= 0) {
			adapter->nr = id;
			return __i2c_add_numbered_adapter(adapter);
		}
	}

	mutex_lock(&core_lock);
	id = idr_alloc(&i2c_adapter_idr, adapter,
		       __i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
	mutex_unlock(&core_lock);
	if (id < 0)
		return id;

	adapter->nr = id;

	return i2c_register_adapter(adapter);
}

5. /memreserve/ 节点作用

start_kernel // init/main.c
    setup_arch(&command_line);  // arch/arm/kernel/setup.c
        arm_memblock_init(mdesc);   // arch/arm/kernel/setup.c
            early_init_fdt_reserve_self();
                    /* Reserve the dtb region */
                    // 把DTB所占区域保留下来, 即调用: memblock_reserve
                    early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
                                      fdt_totalsize(initial_boot_params),
                                      0);           
            early_init_fdt_scan_reserved_mem();  // 根据dtb中的memreserve信息, 调用memblock_reserve
  摘自韦东山设备树详细分析
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值