当前位置: 首页 > 科技观察

Linux设备树的传输及Kernel中设备树的解析

时间:2023-03-12 16:36:27 科技观察

U-Boot加载设备树到内存中指定位置时,ARM内核的SoC使用通用寄存器r2传输地址内存中的dtb。内核获取到地址后,进一步处理dtb文件。设备树的调用是在使用bootm加载内核镜像时(bootz是对bootm的一种封装和功能扩展,本质是一样的)。U-Boot跳转到内核的入口函数是arch/arm/lib下函数boot_jump_linux的C文件,说明设备树的传递方式与SoC架构有关。这个函数在不同的SoCbring-up时非常重要,它是U-Boot和内核之间连接和交换信息的关键API。U-Boot这个函数执行完成后,CPU的控制权就完全交给了内核。/*子命令:GO*/staticvoidboot_jump_linux(bootm_headers_t*images,intflag){...debug("##TransferringcontroltoLinux(ataddress%08lx)"\"...\n",(ulong)kernel_entry);bootstage_mark(BOOTSTAGE_ID_RUN_OS);announce_and_cleanup(fake);if(IMAGE_ENABLE_OF_LIBFDT&&images->ft_len)r2=(unsignedlong)images->ft_addr;elser2=gd->bd->bi_boot_params;...}r2用作存储设备树地址的寄存器,并且它的取值有两种方式,即实例化bootm_header_t这个数据结构的ft_addr,和使用U-Boot的板级启动参数作为设备树的地址。bootm_header_t模式数据结构bootm_header_t的定义如下。它被具有各种内核的SoC使用。每个厂商根据自身CPU的特点,对每个成员进行不同的实例化。/**do_bootm()和do_bootm_()*例程。-Boot需要支持设备树并且文件不为空。ft_len和ft_addr属于bootm_header_t,这两个成员在U-Boot解析image文件时被实例化。函数调用栈如下:do_bootz(structcmd_tbl*cmdtp,intflag,intargc,char*constargv[])-bootz_start()--bootm_find_images(intflag,intargc,char*constargv[],ulongstart,ulongsize)---boot_get_fdt(标志、argc、argv、IH_ARCH_DEFAULT、&images、&images.ft_addr、&images.ft_len);u-boot-v2021.04/common/image-fdt.cgd->bd->bi_boot_params这是比较老的方式,目前基本不用。bi_boot_params是存放内核启动参数的地址,通常在板级初始化时指定。代码执行到这里,r2是否为期望值可以通过打印或者连接调试工具来确认。内核对设备树的解析分为两个阶段。第一阶段是验证和重新调整启动参数;第二阶段是完成设备树的解压,即将设备树从FDT变为EDT,并创建device_node。第一阶段内核启动日志中与设备树相关的第一项打印如下,即打印当前硬件设备的型号名称,“OF:fdt:Machinemodel:V2P-CA9”。BootingLinuxonphysicalCPU0x0Linuxversion5.4.124(qemu@qemu)(gccversion6.5.0(LinaroGCC6.5-2018.12))#3SMPFriJun2515:26:02CST2021CPU:ARMv7Processor[410fc090]revision0(ARMv7),cr=10c5387dCPU:PIPT/VIPTnonaliasingdatacache,VIPTnonaliasinginstructioncacheOF:fdt:Machinemodel:V2P-CA9的型号名称定义在设备树文件的头部,定义了当前设备的整体名称。//SPDX-License-Identifier:GPL-2.0/**ARMLtd.VersatileExpress**CoreTileExpressA9x4*Cortex-A9MPCore(V2P-CA9)**HBI-0191B*//dts-v1/;#include"vexpress-v2m.dtsi"/{model="V2P-CA9";...}但这并不是内核第一次处理设备树。在此之前还有其他操作。函数调用栈如下:setup_arch(char**cmdline_p)arch/arm/kernel/setup.catags_vaddr=FDT_VIRT_BASE(__atags_pointer);setup_machine_fdt(void*dt_virt)arch/arm/kernel/devtree.cearly_init_dt_verify()of_flat_dt_match_machineof()drivers//fdt.cearly_init_dt_scan_nodes();__machine_arch_type=mdesc->nr;第二行,__atags_pointer是dtb在内存中的地址,这个地址是在assembly阶段得到的(如果image是zImage,那么在减压阶段)。由于执行setup_arch时已经启用了mmu并映射了4K段页表,而U-Boot传递给内核的设备树fdt地址是物理地址,所以需要将物理地址转换成虚拟地址。head-common.S.align2.type__mmap_switched_data,%object__mmap_switched_data:#ifdefCONFIG_XIP_KERNEL#ifndefCONFIG_XIP_DEFLATED_DATA.long_sdata@r0.long__data_loc@r1.long_edata_loc@r2#endif.long__bss_stop@sp(temporarystackin.bss)#endif.long__stop1@rslong_start.longinit_thread_union+THREAD_START_SP@sp.longprocessor_id@r0.long__machine_arch_type@r1.long__atags_pointer@r2第一阶段设备树的配置主要包括:A对dtb文件进行crc32校验,检测设备树文件是否合法early_init_dt_verify()Bearly_init_dt_scan_nodes()/*Retrievevariousinformationfromthe/chosennode*/of_scan_flat_dt(early_init_dt_scan_chosen,boot_command_line);/*Initialize{size,address}-cellsinfo*/of_scan_flat_dt(early_init_dt_scan_root,NULL);/*Setupmemory,callingearly_init_dt_add_memory_arch*/of_scan_flat_dt(early_init_dt_scan_memory,NULL);C更新__machine_arch_typeDUpdatechosen上面的chosen信息可以在kernelup之后再次查看以及进行了哪些更改。第二阶段第二阶段简单来说就是解压devicetreeABI文件,由FDT变为EDT,生成对应的device_node节点。这个阶段的函数调用栈如下:unflatten_device_tree();*__unflatten_device_tree()/*Firstpass,scanforsize*/size=unflatten_dt_nodes(blob,NULL,dad,NULL);/*Secondpass,doactualunflattening*/unflatten_dt_nodes(blob,mem,dad,mynodes);unflatten_dt_nodes()populate_node()device_nodes节点如下:device_node创建后,内核根据本阶段完成的工作创建platform_device注册对应的设备,供驱动代码使用。