本设备树解析基于arm平台
从start_kernel开始
linux最底层的初始化部分在HEAD.s中,这是汇编代码,我们暂且不作过多讨论,在head.s完成部分初始化之后,就开始调用C语言函数,而被调用的第一个C语言函数就是start_kernel,start kernel原型是这样的:
asmlinkage __visible void __init start_kernel(void)
{
...
setup_arch(&command_line);
...
}
而对于设备树的处理,基本上就在setup_arch()这个函数中。
在这篇文章中,我们分析的方法就是持续地跟踪linux源代码,但是鉴于linux源代码的复杂性,只将程序中相关性较强的部分贴出来进行分析,因为如果去深究细节部分,那只会自讨苦吃。
博主为整个函数调用流程画了一张思维导图,结合思维导图阅读更加清晰,点此下载,博主也将其贴在了文章最后,需要下载查看,网页上查看可能不清晰。
setup_arch
可以看到,在start_kernel()中调用了setup_arch(&command_line);
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;
mdesc = setup_machine_fdt(__atags_pointer);
...
arm_memblock_init(mdesc);
...
unflatten_device_tree();
...
}
这三个被调用的函数就是主要的设备树处理函数,setup_machine_fdt()函数根据传入的设备树dtb的首地址完成一些初始化操作。
arm_memblock_init()函数主要是内存相关,为设备树保留相应的内存空间,保证设备树dtb本身存在于内存中而不被覆盖。用户可以在设备树中设置保留内存,这一部分同时作了保留指定内存的工作。
unflatten_device_tree()从命名可以看出,这个函数就是对设备树具体的解析,事实上在这个函数中所做的工作就是将设备树各节点转换成相应的struct device_node结构体。
下面我们再来通过代码跟踪仔细分析,先从setup_machine_fdt()开始。
setup_machine_fdt(__atags_pointer)
__atags_pointer这个全局变量存储的就是r2的寄存器值,是设备树在内存中的起始地址,将设备树起始地址传递给setup_machine_fdt,对设备树进行解析。接着跟踪setup_machine_fdt()函数:
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
const struct machine_desc *mdesc, *mdesc_best = NULL;
if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys))) ——————part 1
return NULL;
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); ——————part 2
early_init_dt_scan_nodes(); ——————part 3
...
}
第一部分先将设备树在内存中的物理地址转换为虚拟地址,然后再检查该地址上是否有设备树的魔数(magic),魔数就是一串用于识别的字节码,如果没有或者魔数不匹配,表明该地址没有设备树文件,函数返回,否则验证成功,将设备树地址赋值给全局变量initial_boot_params。
第二部分of_flat_dt_match_machine(mdesc_best, arch_get_next_mach),逐一读取设备树根目录下的compatible属性。
将compatible中的属性一一与内核中支持的硬件单板相对比,匹配成功后返回相应的machine_desc结构体指针。
machine_desc结构体中描述了单板相关的一些硬件信息,这里不过多描述。
主要的的行为就是根据这个compatible属性选取相应的硬件单板描述信息,一般compatible属性名就是"厂商,芯片型号"。
第三部分就是扫描设备树中的各节点,主要分析这部分代码。
void __init early_init_dt_scan_nodes(void)
{
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
of_scan_flat_dt(early_init_dt_sc