kernel解析dtb为节点

kernel解析dtb为节点

head.s入口传递#

回顾#

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

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

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

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

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

Copy
#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

具体这个结构如下

Copy
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

Copy
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#

先记住这个

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

大概的流程是这样的

Copy
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属性指定属性参数的位数

代码浅析

Copy
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/指定一块内存,这块内存就是保留的内存,内核不会占用它。即使你没有指定这块内存,当我们内核启动时,他也会把设备树所占用的区域保留下来.

Copy
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地址,这个是个全局变量

节点的解析#

这段代码的入口是这里

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

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

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

数据结构#

节点描述

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

属性描述

Copy
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

posted @   zongzi10010  阅读(704)  评论(1编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示
CONTENTS