(二)从解析DTS到创建device_DTS的匹配和解析(结合源码)

kernel启动流程_DTS解析(源码层面)

此篇博客有很多参考其他文章的内容,由于参考内容繁杂,不一一标注角标了,在末尾会贴上所有参考博客的link,如有侵权,请联系本人处理,谢谢。

深入,并且广泛
					 -沉默犀牛

我认为作为初学者去学习kernel代码的一个重要方法就是:先知道这些代码是干嘛的,然后再找代码来验证想法。这样的探索顺序会变得事半功倍,让我们直接去看繁杂的代码来分析出代码用途,是非人道主义的。所以此篇博客会先用文字描述一下大致流程,再带着读者到代码中去验证。

执行流程

从dts文件的内容来看,系统平台上挂载了很多总线,i2c,spi,uart等等,每一个总线都被描述为一个节点,Linux启动到kernel 入口后,会执行以下操作来加载系统平台上的总线和设备:start_kernel() ==> setup_arch() ==> unflatten_device_tree(),执行完unflatten_device_tree()后,dts的节点信息被解析出来,保存到allnodes链表中。随后启动到board文件时,调用.init_machine,再调用of_platform_populate(....)接口,加载平台总线和平台设备。至此,系统平台上的所有已配置的总线和设备将被注册到系统中。(对这句话更加正确的解释是:此时所说的设备指的是platform device,此时的总线指的是i2c,spi等,因为i2c总线和spi总线可以理解为注册在platform总线上的device)

注意:不是dtsi文件中所有的节点都会被注册,在注册总线和设备时,会对dts节点的状态作一个判断,如果节点里面的status属性没有被定义,或者status属性被定义了并且值被设为“ok”或者“okay”,其他情况则不被注册到系统中。

那么其他设备,例如i2c、spi设备是怎样注册到系统中的呢?下面我们就以i2c设备为例,看看Linux怎样注册i2c设备到系统中。以高通平台为例,在注册i2c总线时,会调用到qup_i2c_probe()接口,该接口用于申请总线资源和添加i2c适配器。在成功添加i2c适配器后,会调用of_i2c_register_devices()接口。此接口会解析i2c总线节点的子节点(挂载在该总线上的i2c设备节点),获取i2c设备的地址、中断号等硬件信息。然后调用request_module()加载设备对应的驱动文件,调用i2c_new_device(),生成i2c设备。此时设备和驱动都已加载,于是drvier里面的probe方法将被调用。后面流程就和之前一样了。

代码验证

void __init setup_arch(char **cmdline_p)
{
	const struct machine_desc *mdesc;

	setup_processor();
	mdesc = setup_machine_fdt(__atags_pointer);   //根据Device Tree的信息,找到最适合的machine描述符
	if (!mdesc)
		mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
	machine_desc = mdesc;
	machine_name = mdesc->name;
	dump_stack_set_arch_desc("%s", mdesc->name);

	if (mdesc->reboot_mode != REBOOT_HARD)
		reboot_mode = mdesc->reboot_mode;

	init_mm.start_code = (unsigned long) _text;
	init_mm.end_code   = (unsigned long) _etext;
	init_mm.end_data   = (unsigned long) _edata;
	init_mm.brk	   = (unsigned long) _end;

	/* populate cmd_line too for later use, preserving boot_command_line */
	strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
	*cmdline_p = cmd_line;

	early_fixmap_init();
	early_ioremap_init();

	parse_early_param();

#ifdef CONFIG_MMU
	early_paging_init(mdesc);
#endif
	setup_dma_zone(mdesc);
	xen_early_init();
	efi_init();
	/*
	 * Make sure the calculation for lowmem/highmem is set appropriately
	 * before reserving/allocating any mmeory
	 */
	adjust_lowmem_bounds();
	arm_memblock_init(mdesc);
	/* Memory may have been removed so recalculate the bounds. */
	adjust_lowmem_bounds();

	early_ioremap_reset();

	paging_init(mdesc);
	request_standard_resources(mdesc);

	if (mdesc->restart)
		arm_pm_restart = mdesc->restart;

	unflatten_device_tree();   //将DTB转换成节点是device_node的树状结构

我注释的两行代码就是有关DTS解析的重要代码,如注释所述,它们分别做了:
1.根据Device Tree的信息,找到最适合的machine描述符
2.将DTB转换成节点是device_node的树状结构

现在分别仔细看一下实现过程:

1.根据Device Tree的信息,找到最适合的machine_desc,先了解一下结构体machine_desc:
struct machine_desc {
	unsigned int		nr;		/* architecture number	*/
	const char		*name;		/* architecture name	*/
	unsigned long		atag_offset;	/* tagged list (relative) */
	const char *const 	*dt_compat;	/*!!!!本文中最重要的成员!!!!!
    						用于匹配DTS文件*/

	unsigned int		nr_irqs;	/* number of IRQs */

#ifdef CONFIG_ZONE_DMA
	phys_addr_t		dma_zone_size;	/* size of DMA-able area */
#endif

	unsigned int		video_start;	/* start of video RAM	*/
	unsigned int		video_end;	/* end of video RAM	*/

	unsigned char		reserve_lp0 :1;	/* never has lp0	*/
	unsigned char		reserve_lp1 :1;	/* never has lp1	*/
	unsigned char		reserve_lp2 :1;	/* never has lp2	*/
	enum reboot_mode	reboot_mode;	/* default restart mode	*/
	unsigned		l2c_aux_val;	/* L2 cache aux value	*/
	unsigned		l2c_aux_mask;	/* L2 cache aux mask	*/
	void			(*l2c_write_sec)(unsigned long, unsigned);
	const struct smp_operations	*smp;	/* SMP operations	*/
	bool			(*smp_init)(void);
	void			(*fixup)(struct tag *, char **);
	void			(*dt_fixup)(void);
	long long		(*pv_fixup)(void);
	void			(*reserve)(void);/* reserve mem blocks	*/
	void			(*map_io)(void);/* IO mapping function	*/
	void			(*init_early)(void);
	void			(*init_irq)(void);
	void			(*init_time)(void);
	void			(*init_machine)(void);
	void			(*init_late)(void);
#ifdef CONFIG_MULTI_IRQ_HANDLER
	void			(*handle_irq)(struct pt_regs *);
#endif
	void			(*restart)(enum reboot_mode, const char *);
};

接下来看一看setup_machine_fdt()函数的具体实现:

const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
	const struct machine_desc *mdesc, *mdesc_best = NULL;

#if defined(CONFIG_ARCH_MULTIPLATFORM) || defined(CONFIG_ARM_SINGLE_ARMV7M)
	DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
		.l2c_aux_val = 0x0,
		.l2c_aux_mask = ~0x0,
	MACHINE_END

	mdesc_best = &__mach_desc_GENERIC_DT;
#endif

	if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))  /*early_init_dt_verify()检查fdt头部的合法性,然后设置fdt全局变量以
									 及计算crc,赋值了一个initial_boot_params变量后边在访问设备树的时候还会用到*/
		return NULL;

	mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); //重要函数,下面详细介绍

	if (!mdesc) {
		const char *prop;
		int size;
		unsigned long dt_root;

		early_print("\nError: unrecognized/unsupported "
			    "device tree compatible list:\n[ ");

		dt_root = of_get_flat_dt_root();
		prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
		while (size > 0) {
			early_print("'%s' ", prop);
			size -= strlen(prop) + 1;
			prop += strlen(prop) + 1;
		}
		early_print("]\n\n");

		dump_machine_table(); /* does not return */
	}

	/* We really don't want to do this, but sometimes firmware provides buggy data */
	if (mdesc->dt_fixup)
		mdesc->dt_fixup();

	early_init_dt_scan_nodes();

	/* Change machine number to match the mdesc we're using */
	__machine_arch_type = mdesc->nr;

	return mdesc;
}

为了讲清楚mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);这一句话的作用,我们先分析这个函数的第二个参数arch_get_next_match

static const void * __init arch_get_next_mach(const char *const **match)
{
	static const struct machine_desc *mdesc = __arch_info_begin;
	const struct machine_desc *m = mdesc;

	if (m >= __arch_info_end)
		return NULL;

	mdesc++;
	*match = m->dt_compat;
	return m;
}

这个函数很简单,注意的是mdesc是静态局部变量,第一次调用指向__arch_info_begin,后边每次调用都mdesc++,如果超过了__arch_info_end就返回NULL。以上代码说明在__arch_info_begin和__arch_info_end两个地址之间存储着多个machine_desc变量(也可能是一个),该函数遍历这些变量,通过match参数返回所有machine_desc结构体的dt_compat变量指针。

那么__arch_info_begin和__arch_info_end地址是怎么来的呢?在arch/arm/kernel/vmlinux.lds.S连接脚本中定义了.arch.info.init段,__arch_info_begin和__arch_info_end地址分别是该段的首尾地址。

扫描二维码关注公众号,回复: 4740557 查看本文章
     .init.arch.info : {
         __arch_info_begin = .;
         *(.arch.info.init)
         __arch_info_end = .;
     }

那么.init.arch.info段的内容怎么来的呢?这就要参考DT_MACHINE_START和MACHINE_END宏了,arm架构的定义在arch/arm/include/asm/mach/arch.h文件,如下所示:

define DT_MACHINE_START(_name, _namestr)		\
static const struct machine_desc __mach_desc_##_name	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= ~0,				\
	.name		= _namestr,

#endif

从该宏代码看出它定义了一个machine_desc类型的静态局部变量,该变量位于.arch.info.init段中。

我们已经知道了arch_get_next_match指针的具体实现了,现在继续看of_flat_dt_match_machine。

const void * __init of_flat_dt_match_machine(const void *default_match,
		const void * (*get_next_compat)(const char * const**))
{
	const void *data = NULL;
	const void *best_data = default_match;
	const char *const *compat;
	unsigned long dt_root;
	unsigned int best_score = ~1, score = 0;

	dt_root = of_get_flat_dt_root();
	 /*遍历.arch.info.init段中所有的dt_compat变量,  然后通过of_flat_dt_match计算一个分数,
	 并且寻找那个score最小的*/
	while ((data = get_next_compat(&compat))) {
		score = of_flat_dt_match(dt_root, compat);
		if (score > 0 && score < best_score) {
			best_data = data;
			best_score = score;
		}
	}
	if (!best_data) {
		const char *prop;
		int size;

		pr_err("\n unrecognized device tree list:\n[ ");

		prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
		if (prop) {
			while (size > 0) {
				printk("'%s' ", prop);
				size -= strlen(prop) + 1;
				prop += strlen(prop) + 1;
			}
		}
		printk("]\n\n");
		return NULL;
	}

	pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());

	return best_data;
}

of_flat_dt_match_machine的其余部分代码都是出错处理及打印,现在我们看of_flat_dt_match的实现,该函数仅仅是直接调用of_fdt_match而已,不同的是增加了initial_boot_params参数(还记得我们说过前边说过的这个变量的初始化吧,其实这就是内核中的一个简单封装而已)。

int __init of_flat_dt_match(unsigned long node, const char *const *compat)
{
	return of_fdt_match(initial_boot_params, node, compat);
}
int of_fdt_match(const void *blob, unsigned long node,
                 const char *const *compat)
{
	unsigned int tmp, score = 0;

	if (!compat)
		return 0;

	while (*compat) {
		tmp = of_fdt_is_compatible(blob, node, *compat);
		if (tmp && (score == 0 || (tmp < score)))
			score = tmp;
		compat++;
	}

	return score;
}

这个函数就是遍历compat数组的每一个字符串,然后通过of_fdt_is_compatible函数计算匹配度(以最小的数值作为最终的结果)。代码到这个地方已经很好理解了,compat中的数据来自内核的.arch.info.init段,这个段表示内核支持的平台,blob是设备树起始地址,通过node节点指定根节点的compatible属性,然后计算匹配度。还记得我们前边说过的compatible属性包含多个字符串,从前向后范围越来越大,优先匹配前边的,这个地方代码计算分数(score变量)就是这个目的。

继续看一下of_fdt_is_compatible()函数:

int of_fdt_is_compatible(const void *blob,
		      unsigned long node, const char *compat)
{
	const char *cp;
	int cplen;
	unsigned long l, score = 0;

	cp = fdt_getprop(blob, node, "compatible", &cplen);
	if (cp == NULL)
		return 0;
	while (cplen > 0) {
		score++;
		if (of_compat_cmp(cp, compat, strlen(compat)) == 0)
			return score;
		l = strlen(cp) + 1;
		cp += l;
		cplen -= l;
	}

	return 0;
}

这个函数就是比较compatible属性值了,等到while中的if条件满足,返回score,即代表找到了最匹配的DTS,再总结一下就是:内核通过"compatible"属性找到对应的平台描述信息,按照范围从小到大尽量匹配范围最小的,如果匹配不到,那么说明内核不支持该平台,系统将在初始化的时候就出错。

那么到这里,我们就整个完成了我们这一小节的主题:根据Device Tree的信息,找到最适合的machine_desc。

2.将DTB转换成节点是device_node的树状结构:

在此之前,我们先了解一下device_node结构体:

	struct device_node {
        const char *name;----------------------device node name
        const char *type;-----------------------对应device_type的属性
        phandle phandle;-----------------------对应该节点的phandle属性
        const char *full_name; ----------------从“/”开始的,表示该node的full path

        struct    property *properties;-------------该节点的属性列表
        struct    property *deadprops; ----------如果需要删除某些属性,kernel并非真的删除,而是挂入到deadprops的列表
        struct    device_node *parent;------parent、child以及sibling将所有的device node连接起来
        struct    device_node *child;
        struct    device_node *sibling;
        struct    device_node *next;  --------通过该指针可以获取相同类型的下一个node
        struct    device_node *allnext;-------通过该指针可以获取node global list下一个node
        struct    proc_dir_entry *pde;--------开放到userspace的proc接口信息
        struct    kref kref;-------------该node的reference count
        unsigned long _flags;
        void    *data;
    };
    void __init unflatten_device_tree(void)
    {
        //解析设备树,将所有的设备节点链入全局链表of_allnodes中
        __unflatten_device_tree(initial_boot_params,&of_allnodes,early_init_dt_alloc_memory_arch);
        
        //设置内核输出终端,以及遍历“/aliases”节点下的所有的属性,挂入相应链表
        of_alias_scan(early_init_dt_alloc_memory_arch);
    }

分析以上代码,在unflatten_device_tree()中,调用函数__unflatten_device_tree(),参数initial_boot_params指向Device Tree在内存中的首地址,of_root在经过该函数处理之后,会指向根节点,early_init_dt_alloc_memory_arch是一个函数指针,为struct device_node和struct property结构体分配内存的回调函数(callback)。

__unflatten_device_tree()函数中,两次调用unflatten_dt_node()函数,第一次是为了得到Device Tree转换成struct device_node和struct property结构体需要分配的内存大小,这时候可能递归调用自身(如有子node的话)。第二次调用才是具体填充每一个struct device_node和struct property结构体。

static void __unflatten_device_tree(struct boot_param_header*blob,
                                        struct device_node **mynodes,
                                        void *(*dt_alloc)(u64 size, u64 align))
    {
        unsigned long size;
        void *start,*mem;
        struct device_node **allnextp= mynodes;
        
        pr_debug(" -> unflatten_device_tree()\n");
        
        if (!blob){
            pr_debug("No device tree pointer\n");
            return;
        }
        
        pr_debug("Unflattening device tree:\n");
        pr_debug("magic: %08x\n", be32_to_cpu(blob->magic));
        pr_debug("size: %08x\n", be32_to_cpu(blob->totalsize));
        pr_debug("version: %08x\n", be32_to_cpu(blob->version));
        
        //检查设备树magic
        if (be32_to_cpu(blob->magic)!= OF_DT_HEADER){
            pr_err("Invalid device tree blob header\n");
            return;
        }
        
        //找到设备树的设备节点起始地址
        start = ((void*)blob)+ be32_to_cpu(blob->off_dt_struct);
        //第一次调用mem传0,allnextpp传NULL,实际上是为了计算整个设备树所要的空间
        size = (unsigned long)unflatten_dt_node(blob, 0,&start, NULL, NULL, 0);
        size = ALIGN(size, 4);//4字节对齐
        
        pr_debug(" size is %lx, allocating...\n", size);
        
        //调用early_init_dt_alloc_memory_arch函数,为设备树分配内存空间
        mem = dt_alloc(size+ 4, __alignof__(struct device_node));
        memset(mem, 0, size);
        
        //设备树结束处赋值0xdeadbeef,为了后边检查是否有数据溢出
        *(__be32*)(mem+ size) = cpu_to_be32(0xdeadbeef);
        pr_debug(" unflattening %p...\n", mem);
        
        //再次获取设备树的设备节点起始地址
        start = ((void*)blob)+ be32_to_cpu(blob->off_dt_struct);
        //mem为设备树分配的内存空间,allnextp指向全局变量of_allnodes,生成整个设备树
        unflatten_dt_node(blob, mem,&start, NULL, &allnextp, 0);
        if (be32_to_cpup(start)!= OF_DT_END)
            pr_warning("Weird tag at end of tree: %08x\n", be32_to_cpup(start));
        if (be32_to_cpup(mem+ size) != 0xdeadbeef)
            pr_warning("End of tree marker overwritten: %08x\n",be32_to_cpup(mem+ size));
        *allnextp = NULL;
        
        pr_debug(" <- unflatten_device_tree()\n");
    }

可以看到主要起作用的函数是 unflatten_dt_node(),接下来进入这个函数看一下:

static void * unflatten_dt_node(struct boot_param_header*blob,
                                    void *mem,void**p,
                                    struct device_node *dad,
                                    struct device_node ***allnextpp,
                                    unsigned long fpsize)
    {
        struct device_node *np;
        struct property *pp, **prev_pp= NULL;
        char *pathp;
        u32 tag;
        unsigned int l, allocl;
        int has_name = 0;
        int new_format = 0;
        
        //*p指向设备树的设备块起始地址
        tag = be32_to_cpup(*p);
        //每个有孩子的设备节点,其tag一定是OF_DT_BEGIN_NODE
        if (tag!= OF_DT_BEGIN_NODE){
            pr_err("Weird tag at start of node: %x\n", tag);
            return mem;
        }

        *p += 4;//地址+4,跳过tag,这样指向节点的名称或者节点路径名
        pathp = *p;//获得节点名或者节点路径名
        l = allocl = strlen(pathp)+ 1;//该节点名称的长度
        *p = PTR_ALIGN(*p+ l, 4);//地址对齐后,*p指向该节点属性的地址
        
        //如果是节点名则进入,若是节点路径名则(*pathp)== '/'
        if ((*pathp)!= '/'){
            new_format = 1;
            if (fpsize== 0){//fpsize=0
                fpsize = 1;
                allocl = 2;
                l = 1;
                *pathp = '\0';
            } else{
                fpsize += l;//代分配的长度=本节点名称长度+父亲节点绝对路径的长度
                allocl = fpsize;
            }
        }
        
        //分配一个设备节点device_node结构,*mem记录分配了多大空间,最终会累加计算出该设备树总共分配的空间大小
        np = unflatten_dt_alloc(&mem, sizeof(struct device_node)+ allocl,__alignof__(struct device_node));

        //第一次调用unflatten_dt_node时,allnextpp=NULL
        //第二次调用unflatten_dt_node时,allnextpp指向全局变量of_allnodes的地址
        if (allnextpp){
            char *fn;
            //full_name保存完整的节点名,即包括各级父节点的名称
            np->full_name= fn = ((char *)np)+ sizeof(*np);
            //若new_format=1,表示pathp保存的是节点名,而不是节点路径名,所以需要加上父节点的name
            if (new_format){
                if (dad && dad->parent){
                    strcpy(fn, dad->full_name);//把父亲节点绝对路径先拷贝
                    fn += strlen(fn);
                }
                *(fn++)= '/';
            }
            memcpy(fn, pathp, l);//拷贝本节点的名称
        
            //prev_pp指向节点的属性链表
            prev_pp = &np->properties;

            //当前节点插入全局链表of_allnodes
            **allnextpp= np;
            *allnextpp = &np->allnext;

            //若父亲节点不为空,则设置该节点的parent
            if (dad!= NULL) {
                np->parent= dad;//指向父亲节点
                if (dad->next== NULL)//第一个孩子
                    dad->child= np;//child指向第一个孩子
                else
                    dad->next->sibling= np;//把np插入next,这样孩子节点形成链表
                dad->next= np;
            }
            kref_init(&np->kref);
        }

        //分析该节点的属性
        while (1){
            u32 sz, noff;
            char *pname;
        
            //前边已经将*p移到指向节点属性的地址处,取出属性标识
            tag = be32_to_cpup(*p);
            //空属性,则跳过
            if (tag== OF_DT_NOP){
                *p += 4;
                continue;
            }
            //tag不是属性则退出,对于有孩子节点退出时为OF_DT_BEGIN_NODE,对于叶子节点退出时为OF_DT_END_NODE
            if (tag!= OF_DT_PROP)
                break;
            //地址加4,跳过tag
            *p += 4;
            //获得属性值的大小,是以为占多少整形指针计算的
            sz = be32_to_cpup(*p);
            //获取属性名称在节点的字符串块中的偏移
            noff = be32_to_cpup(*p+ 4);
            //地址加8,跳过属性值的大小和属性名称在节点的字符串块中的偏移
            *p += 8;
            //地址对齐后,*P指向属性值所在的地址
            if (be32_to_cpu(blob->version)< 0x10)
                *p = PTR_ALIGN(*p, sz>= 8 ? 8 : 4);
            
            //从节点的字符串块中noff偏移处,得到该属性的name
            pname = of_fdt_get_string(blob, noff);
            if (pname== NULL) {
                pr_info("Can't find property name in list !\n");
                break;
            }

            //如果有名称为name的属性,表示变量has_name为1
            if (strcmp(pname,"name") == 0)
                has_name = 1;
            //计算该属性name的大小
            l = strlen(pname)+ 1;

            //为该属性分配一个属性结构,即struct property,
            //*mem记录分配了多大空间,最终会累加计算出该设备树总共分配的空间大小
            pp = unflatten_dt_alloc(&mem, sizeof(structproperty),__alignof__(structproperty));

            //第一次调用unflatten_dt_node时,allnextpp=NULL
            //第二次调用unflatten_dt_node时,allnextpp指向全局变量of_allnodes的地址
            if (allnextpp){
                if ((strcmp(pname,"phandle") == 0)|| (strcmp(pname,"linux,phandle")== 0)){
                    if (np->phandle== 0)
                        np->phandle= be32_to_cpup((__be32*)*p);
                }
                if (strcmp(pname,"ibm,phandle")== 0)
                    np->phandle= be32_to_cpup((__be32*)*p);
                pp->name= pname;//属性名
                pp->length= sz;//属性值长度
                pp->value= *p;//属性值

                //属性插入该节点的属性链表np->properties
                *prev_pp = pp;
                prev_pp = &pp->next;
            }
            *p = PTR_ALIGN((*p)+ sz, 4);//指向下一个属性
        }
        //至此遍历完该节点的所有属性

        //如果该节点没有"name"的属性,则为该节点生成一个name属性,插入该节点的属性链表
        if (!has_name){
            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(structproperty) + sz,__alignof__(structproperty));
            if (allnextpp){
                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);
            }
        }

        //若设置了allnextpp指针
        if (allnextpp){
            *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>";
        }

        //前边在遍历属性时,tag不是属性则退出
        //对于有孩子节点退出时tag为OF_DT_BEGIN_NODE,对于叶子节点退出时tag为OF_DT_END_NODE
        while (tag== OF_DT_BEGIN_NODE|| tag == OF_DT_NOP){
            //空属性则指向下个属性
            if (tag== OF_DT_NOP)
                *p += 4;
            else
                //OF_DT_BEGIN_NODE则表明其还有子节点,所以递归分析其子节点
                mem = unflatten_dt_node(blob, mem, p, np, allnextpp,fpsize);
            tag = be32_to_cpup(*p);
        }

        //对于叶子节点或者分析完成
        if (tag!= OF_DT_END_NODE){
            pr_err("Weird tag at end of node: %x\n", tag);
            return mem;
        }
        *p += 4;
        //mem返回整个设备树所分配的内存大小,即设备树占的内存空间
        return mem;
    }

至此已经将DTB转换成节点是device_node的树状结构了。
下一篇文章会补充device_node是如何成为注册到各个bus上的device

参考文章:
我眼中的linux设备数系列 :https://www.cnblogs.com/targethero/p/5086085.html
Device Tree(三)代码分析:http://www.wowotech.net/linux_kenrel/dt-code-analysis.html
宋牧春:Linux设备树文件结构与解析深度分析:http://www.360doc.com/content/18/0926/15/60139132_789843621.shtml
linux device tree源代码解析 :https://www.cnblogs.com/sky-heaven/p/6742033.html
Linux DTS(Device Tree Source)设备树详解之二(dts匹配及发挥作用的流程篇):https://blog.csdn.net/radianceblau/article/details/74722395
(DT系列五)Linux kernel 是怎么将 devicetree中的内容生成plateform_device:https://blog.csdn.net/lichengtongxiazai/article/details/38942033

猜你喜欢

转载自blog.csdn.net/qq_35065875/article/details/82852902
DTS