参考:linux设备驱动程序-设备树(1)-dtb转换成device_node - 牧野星辰 - 博客园
内核启动后如何调用和解析设备树二进制文件dtb的
在linux-3.4.y/init/main.c中,linux内核启动后调用start_kernel函数; 其中有setup_arch函数,对于arm平台会调用arch/arm/kernel/setup.c文件中的setup_arch();
void __init setup_arch(char **cmdline_p)
{
struct machine_desc *mdesc;
setup_processor();
//根据传入的设备树dtb的首地址完成一些初始化操作;
//__atags_pointer参数这个全局变量就是r2的寄存器值;是设备树在内存中的起始地址;
mdesc = setup_machine_fdt(__atags_pointer);
if (!mdesc)
mdesc = setup_machine_tags(machine_arch_type);
machine_desc = mdesc;
machine_name = mdesc->name;
if (mdesc->restart_mode)
reboot_setup(&mdesc->restart_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;
parse_early_param();
sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL);
sanity_check_meminfo();
//内存相关,为设备树保留相应的内存空间,保证设备树dtb本身在内存中而不被覆盖,用户可在设备树中保留内存;
arm_memblock_init(&meminfo, mdesc);
...
//展开设备树,实际上就是解析dtb,并转换为struct device_node结构体;
unflatten_device_tree();
...
}
1. setup_machine_fdt函数
函数作用: 如果在r2中向内核传递了一个dtb,那么使用它来选择正确的machine_desc并设置系统。
struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
struct boot_param_header *devtree;
struct machine_desc *mdesc, *mdesc_best = NULL;
unsigned int score, mdesc_score = ~1;
unsigned long dt_root;
const char *model;
if (!dt_phys)
return NULL;
//物理地址转虚拟地址;
devtree = phys_to_virt(dt_phys);
/* check device tree validity */
//验证设备树有效性
if (be32_to_cpu(devtree->magic) != OF_DT_HEADER)
return NULL;
/* Search the mdescs for the 'best' compatible value match */
initial_boot_params = devtree;
//获取根节点
dt_root = of_get_flat_dt_root();
//循环读取设备树根目录下的compatible属性;
for_each_machine_desc(mdesc) {
//mdesc->dt_compat 为设备树compatible字符数组
score = of_flat_dt_match(dt_root, mdesc->dt_compat);
if (score > 0 && score < mdesc_score) {
mdesc_best = mdesc;
mdesc_score = score;
}
}
...
//获取根节点下的model属性
model = of_get_flat_dt_prop(dt_root, "model", NULL);
if (!model)
model = of_get_flat_dt_prop(dt_root, "compatible", NULL);
if (!model)
model = "<unknown>";
pr_info("Machine: %s, model: %s\n", mdesc_best->name, model);
//扫描设备树中的各节点
//分别是处理choose节点的处理,root节点中除了子节点外的属性信息,memory节点;
/* Retrieve various information from the /chosen node */
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
/* Initialize {size,address}-cells info */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
/* Setup memory, calling early_init_dt_add_memory_arch */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
/* Change machine number to match the mdesc we're using */
__machine_arch_type = mdesc_best->nr;
return mdesc_best;
}
其中的of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);语句是将boot_command_line作为回调函数early_init_dt_scan_chosen的参数传入,并拷贝从choose属性读取到的命令行参数到boot_command_line中;
of_scan_flat_dt(early_init_dt_scan_root, NULL);语句是查找#size-cells和#address-cells属性,并赋值全局dt_root_size_cells和dt_root_addr_cells ;
of_scan_flat_dt(early_init_dt_scan_memory, NULL);语句主要获取memory相关信息计算后,调用early_init_dt_add_memory_arch(base, size);分配空间;
2. arm_memblock_init函数
void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc)
{
int i;
for (i = 0; i < mi->nr_banks; i++)
memblock_add(mi->bank[i].start, mi->bank[i].size);
/* Register the kernel text, kernel data and initrd with memblock. */
memblock_reserve(__pa(_stext), _end - _stext);
...
if (phys_initrd_size) {
memblock_reserve(phys_initrd_start, phys_initrd_size);
/* Now convert initrd to virtual addresses */
initrd_start = __phys_to_virt(phys_initrd_start);
initrd_end = initrd_start + phys_initrd_size;
}
arm_mm_memblock_reserve();
arm_dt_memblock_reserve();
arm_memblock_steal_permitted = false;
memblock_allow_resize();
memblock_dump_all();
}
主要是为设备树分配保留内存空间;
3. unflatten_device_tree函数
void __init unflatten_device_tree(void)
{
__unflatten_device_tree(initial_boot_params, &allnodes,
early_init_dt_alloc_memory_arch);
/* Get pointer to "/chosen" and "/aliasas" nodes for use everywhere */
of_alias_scan(early_init_dt_alloc_memory_arch);
}
__unflatten_device_tree函数中,扫描得出设备树转换成device node需要的空间,然后系统申请内存空间,第二次就进行真正的解析工作;代码如下:
static void __unflatten_device_tree(struct boot_param_header *blob,
struct device_node **mynodes,
void * (*dt_alloc)(u64 size, u64 align))
{
unsigned long start, mem, size;
struct device_node **allnextp = mynodes;
...
/* 获取大小 */
start = ((unsigned long)blob) +
be32_to_cpu(blob->off_dt_struct);
size = unflatten_dt_node(blob, 0, &start, NULL, NULL, 0);
size = (size | 3) + 1;
pr_debug(" size is %lx, allocating...\n", size);
/* Allocate memory for the expanded device tree */
//分配空间
mem = (unsigned long)
dt_alloc(size + 4, __alignof__(struct device_node));
((__be32 *)mem)[size / 4] = cpu_to_be32(0xdeadbeef);
pr_debug(" unflattening %lx...\n", mem);
/* Second pass, do actual unflattening */
//实际解析工作
start = ((unsigned long)blob) +
be32_to_cpu(blob->off_dt_struct);
unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0);
...
}
参数allnodes就是struct device_node指针,将其传入进入后,通过解析设备树对其赋值填充;
of_alias_scan函数中:
void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
{
struct property *pp;
//获取根下的chosen节点属性
of_chosen = of_find_node_by_path("/chosen");
if (of_chosen == NULL)
of_chosen = of_find_node_by_path("/chosen@0");
//获取aliases节点
of_aliases = of_find_node_by_path("/aliases");
if (!of_aliases)
return;
//遍历所有aliases节点
for_each_property_of_node(of_aliases, pp) {
const char *start = pp->name;
const char *end = start + strlen(start);
struct device_node *np;
struct alias_prop *ap;
int id, len;
/* Skip those we do not want to proceed */
//过滤不想处理的
if (!strcmp(pp->name, "name") ||
!strcmp(pp->name, "phandle") ||
!strcmp(pp->name, "linux,phandle"))
continue;
np = of_find_node_by_path(pp->value);
if (!np)
continue;
/* walk the alias backwards to extract the id and work out
* the 'stem' string */
while (isdigit(*(end-1)) && end > start)
end--;
len = end - start;
if (kstrtoint(end, 10, &id) < 0)
continue;
/* Allocate an alias_prop with enough space for the stem */
//分配空间
ap = dt_alloc(sizeof(*ap) + len + 1, 4);
if (!ap)
continue;
ap->alias = start;
//将所有aliases内容添加到链表
of_alias_add(ap, np, id, start, len);
}
}