zoukankan      html  css  js  c++  java
  • kernel解析dtb为节点


    title: 解析dtb为节点
    date: 2019/4/26 14:02:18
    toc: true

    kernel解析dtb为节点

    head.s入口传递

    回顾

    看以前的笔记 kernel(二)源码浅析

    先来回顾下以前uboot是怎么传递参数的?

    R0 一般设置为0
    R1 machine id (设备树不使用)
    R2 ATAGS(设备树使用为DTB地址)

    kernel的入口点是``archarmkernelhead.S,以前的流程是根据这个machine id去匹配到具体的单板,然后使用ATAGS`构造相应的启动参数

    使用机器id可以找到类似如下的结构体

    #define MACHINE_START(_type,_name)			
    static const struct machine_desc __mach_desc_##_type	
     __used							
     __attribute__((__section__(".arch.info.init"))) = {	
    	.nr		= MACH_TYPE_##_type,		
    	.name		= _name,
    
    #define MACHINE_END				
    };
    
    MACHINE_START(S3C2440, "SMDK2440")
    	/* Maintainer: Ben Dooks <ben@fluff.org> */
    	.phys_io	= S3C2410_PA_UART,
    	.io_pg_offst	= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    	.boot_params	= S3C2410_SDRAM_PA + 0x100,
    
    	.init_irq	= s3c24xx_init_irq,
    	.map_io		= smdk2440_map_io,
    	.init_machine	= smdk2440_machine_init,
    	.timer		= &s3c24xx_timer,
    MACHINE_END
    

    具体这个结构如下

     struct machine_desc {
    	/*
    	 * Note! The first four elements are used
    	 * by assembler code in head-armv.S
    	 */
    	unsigned int		nr;		/* architecture number	*/
    	unsigned int		phys_io;	/* start of physical io	*/
    	unsigned int		io_pg_offst;	/* byte offset for io 
    						 * page tabe entry	*/
    
    	const char		*name;		/* architecture name	*/
    	unsigned long		boot_params;	/* tagged list		*/
    
    	unsigned int		video_start;	/* start of video RAM	*/
    	unsigned int		video_end;	/* end of video RAM	*/
    
    	unsigned int		reserve_lp0 :1;	/* never has lp0	*/
    	unsigned int		reserve_lp1 :1;	/* never has lp1	*/
    	unsigned int		reserve_lp2 :1;	/* never has lp2	*/
    	unsigned int		soft_reboot :1;	/* soft reboot		*/
    	void			(*fixup)(struct machine_desc *,
    					 struct tag *, char **,
    					 struct meminfo *);
    	void			(*map_io)(void);/* IO mapping function	*/
    	void			(*init_irq)(void);
    	struct sys_timer	*timer;		/* system tick timer	*/
    	void			(*init_machine)(void);
    };
    

    设备树启动浅析

    head.s会调用head-common.S

    a. __lookup_processor_type : 使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息)
    b. __vet_atags  : 判断是否存在可用的ATAGS或DTB
    c. __create_page_tables  : 创建页表, 即创建虚拟地址和物理地址的映射关系
    d. __enable_mmu  : 使能MMU, 以后就要使用虚拟地址了
    e. __mmap_switched  : 上述函数里将会调用__mmap_switched
    	.long	__bss_start			@ r0
    	.long	__bss_stop			@ r1
    	.long	init_thread_union + THREAD_START_SP @ sp
    
    	.long	processor_id			@ r0
    	.long	__machine_arch_type		@ r1
    	.long	__atags_pointer			@ r2
    f. 把bootloader传入的r2参数, 保存到变量__atags_pointer中
    g. 调用C函数start_kernel
    

    start_kernel

    先记住这个

    	.long	processor_id			@ r0
    	.long	__machine_arch_type		@ r1
    	.long	__atags_pointer			@ r2
    

    大概的流程是这样的

    mdesc = setup_machine_fdt(__atags_pointer);
    	// 头部检查
    	early_init_dt_verify
    	//找到最匹配的machine_desc
    	of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
    
    if (!mdesc) //按照以前使用atag的方式
    	mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
    
    // 找到匹配的machine_desc,后处理了	
    machine_desc = mdesc;
    machine_name = mdesc->name;
    dump_stack_set_arch_desc("%s", mdesc->name);
    // 把命令行启动参数存起来
    /* populate cmd_line too for later use, preserving boot_command_line */
    strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
    *cmdline_p = cmd_line;
    
    // 保留dtb 本身的内存 以及指定的 reserve的内存
    arm_memblock_init(mdesc);
    

    启动参数以及内存解析

    • /chosen节点中bootargs属性就是内核启动的命令行参数,它里面可以指定根文件系统在哪里,第一个运行的应用程序是哪一个,指定内核的打印信息从哪个设备里打印出来,存在boot_command_line

    • /memory中的reg属性指定了不同板子内存的大小和起始地址,调用memblock_add

    • 根节点的#address-cells和#size-cells属性指定属性参数的位数

    代码浅析

    setup_machine_fdt
    
    	mdesc_best = &__mach_desc_GENERIC_DT;  这个应该是机器描述符段的起始地址
    
    	early_init_dt_verify
    		// 头部校验
    		fdt_check_header
    		// 把这个 地址又存一遍 initial_boot_params
    		initial_boot_params = params;
    		//计算crc到 of_fdt_crc32
    		of_fdt_crc32=crc32_be(...)
    	mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
    		// arch_get_next_mach 获取下一个机器描述
    		
    		// 寻找下一个机器描述的id 
    		// get_next_compat=arch_get_next_mach
    		// 最终找到最匹配的机器描述
    		while ((data = get_next_compat(&compat))) 
    		{
    			score = of_flat_dt_match(dt_root, compat);
    					// initial_boot_params 就是上面存档的dtb地址
    					of_fdt_match(initial_boot_params, node, compat);
    						of_fdt_is_compatible
    							// 寻找  compatible 属性来匹配 单板
    							fdt_getprop(blob, node, "compatible", &cplen);
    							// 找到最匹配的
    							while (cplen > 0)
    								of_compat_cmp
    					
    			if (score > 0 && score < best_score)
    			{
    				best_data = data;
    				best_score = score;
    			}
    		}
    
    	early_init_dt_scan_nodes	
    		// 找到启动命令参数
    		/* Retrieve various information from the /chosen node */
    		of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
    			early_init_dt_scan_chosen // 寻找到 chosen 的 bootargs
    				p = of_get_flat_dt_prop(node, "bootargs", &l);
    		// 找到reg的描述	
    		/* Initialize {size,address}-cells info */
    		of_scan_flat_dt(early_init_dt_scan_root, NULL);
    			prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
    			prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
    		// 内存相关设置
    		/* Setup memory, calling early_init_dt_add_memory_arch */
    		of_scan_flat_dt(early_init_dt_scan_memory, NULL);
    			early_init_dt_scan_memory
    				if( ! of_get_flat_dt_prop(node, "linux,usable-memory", &l);)
    				else of_get_flat_dt_prop(node, "reg", &l);
    				....
    			// 	
    			of_get_flat_dt_prop(node, "hotpluggable", NULL);
    

    内存保留

    uboot把设备树DTB文件随便放到内存的某一个地方就可以使用,内核不会去覆盖DTB所占用的那块内存呢.在设备树文件中,可以使用/memreserve/指定一块内存,这块内存就是保留的内存,内核不会占用它。即使你没有指定这块内存,当我们内核启动时,他也会把设备树所占用的区域保留下来.

    setup_arch
    	// dtb 判断,机器id查找
    	setup_machine_fdt
    	
    	// 内存保留
    	arm_memblock_init(mdesc);
    		// dtb 自身的内存空间
    		early_init_fdt_reserve_self();
    			early_init_dt_reserve_memory_arch(
    						 __pa(initial_boot_params),				//initial_boot_params 是以前存储的dtb地址
    						  fdt_totalsize(initial_boot_params),	//头部指示的大小
    						  0);
    				memblock_reserve(....)
    		// dtb 描述中的 reserve 内存
    		early_init_fdt_scan_reserved_mem();
    			fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
    				early_init_dt_reserve_memory_arch
    					memblock_reserve(....)
    

    小结

    综上,我们到此为止解析了如下

    chosen/bootargs 启动命令行boot_command_line
    memory 保留内存
    dtb本身内存
    initial_boot_params dtb地址,这个是个全局变量

    节点的解析

    这段代码的入口是这里

    setup_arch
    	// 找到匹配的机器描述
    	setup_machine_fdt
    	// 内存保留
    	arm_memblock_init(mdesc);
    	// 节点解析
    	unflatten_device_tree();
    
    unflatten_device_tree()
    {
        // 第一次解析,这里最后一个参数是false,不会分配内存,会计算所需的内存大小
    	__unflatten_device_tree(initial_boot_params, NULL, &of_root,
    				early_init_dt_alloc_memory_arch, false);	
    	
    	/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
    	of_alias_scan(early_init_dt_alloc_memory_arch);
    
        // 这里会真正分配内存,存放结构
    	unittest_unflatten_overlay_base();
    	
    }
    

    这段代码还是比较复杂了,暂时不去分析了这里没有使用递归,按我的理解应该是类似这样的

    for(next_node(xxx))-----这里的next_node 会记录树的深度,也就是应该会有父兄的记录
    {
    	// 解析status
        
        // 解析属性
        if(is_not_属性)
            continue;
    
    }
    

    数据结构

    节点描述

    struct device_node {
        const char *name;  // 来自节点中的name属性, 如果没有该属性, 则设为"NULL"
        const char *type;  // 来自节点中的device_type属性, 如果没有该属性, 则设为"NULL"
        phandle phandle;
        const char *full_name;  // 节点的名字, node-name[@unit-address]
        struct fwnode_handle fwnode;
    
        struct  property *properties;  // 节点的属性
        struct  property *deadprops;    /* removed properties */
        struct  device_node *parent;   // 节点的父亲
        struct  device_node *child;    // 节点的孩子(子节点)
        struct  device_node *sibling;  // 节点的兄弟(同级节点)
        #if defined(CONFIG_OF_KOBJ)
        struct  kobject kobj;
        #endif
        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;    // 属性名字, 指向dtb文件中的字符串
        int length;       // 属性值的长度
        void    *value;   // 属性值, 指向dtb文件中value所在位置, 数据仍以big endian存储
        struct property *next;
        #if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
        unsigned long _flags;
        #endif
        #if defined(CONFIG_OF_PROMTREE)
        unsigned int unique_id;
        #endif
        #if defined(CONFIG_OF_KOBJ)
        struct bin_attribute attr;
        #endif
    };
    

    具体的描述看下老师的图,很容易理解,就是一个比较大的链表,注意其中节点名字指向节点本身最后的内存

    mark

  • 相关阅读:
    【转】PowerManager 与 WakeLock
    【转】设计模式总结之模式分类
    【转】一篇文章,教你学会Git
    【转】Iconfont
    【转】码云source tree 提交超过100m 为什么大文件推不上去
    各 Android 平台版本支持的 API 级别
    【转】Android进程机制
    【转】数据库CRUD操作
    【转】数据库--视图的基本概念以及作用
    动态规划的两种形式
  • 原文地址:https://www.cnblogs.com/zongzi10010/p/10793075.html
Copyright © 2011-2022 走看看