dtb文件转换device node
本设备树解析基于linux3.0.4内核版本
start kernel()
从start kernel开始,内核调用的第一个C函数,函数原型如下,函数路径见./init/main.c。
asmlinkage void __init start_kernel(void)
{
.............
setup_arch(&command_line);
.............
}
setup_arch()
继续追踪setup_arch()函数实体,函数原型如下,函数路径见
./arch/mips/kernel/setup.c
void __init setup_arch(char **cmdline_p)
{
.............
arch_mem_init(cmdline_p);
.............
}
arch_mem_init()
继续追踪arch_mem_init()函数实体,函数原型如下,函数路径见
./arch/mips/kernel/setup.c。经过层层调用,我们终于看到设备树初始化函数,就是在arch_mem_init()函数进行了两次初始化,第一次是plat_mem_setup();第二次是device_tree_init();
static void __init arch_mem_init(char **cmdline_p)
{
.............
plat_mem_setup();
.............
device_tree_init();
.............
}
plat_mem_setup()
继续追踪plat_mem_setup()函数实体,函数原型如下,函数路径见
./arch/mips/xxx/common/setup.c。进行设备树早期初始化功能,其中xxx_fdt_blob这个变量表示指定设备树dtb文件在内存存放的首地址,经过head.S文件内汇编代码fw_a2寄存器值解析转化而来。通常uboot环境下,bootm加载内核的第三个参数就是设备树dtb的首地址,其值存到fw_a2寄存器里。详细了解见内核加载的第一个汇编文件head.S,文件路径见./arch/mips/kernel/head.S
void __init plat_mem_setup(void)
{
.............
__dt_setup_arch(xxx_fdt_blob);
.............
}
__dt_setup_arch()
继续追踪__dt_setup_arch()函数实体,函数原型如下,函数路径见
./arch/mips/kernel/prom.c。第一步校验dtb文件合法性,第二步赋值首地址和全局变量,第三步进行设备树早期初始化。
void __init __dt_setup_arch(struct boot_param_header *bph)
{
if (be32_to_cpu(bph->magic) != OF_DT_HEADER) {
pr_err("DTB has bad magic, ignoring builtin OF DTB\n");
return;
}
initial_boot_params = bph;
early_init_devtree(initial_boot_params);
}
early_init_devtree()
继续追踪 early_init_devtree()函数实体,函数原型如下,函数路径见
./arch/mips/kernel/prom.c,到这里就是设备树早期初始化的重点了,下面挨个逐一分析设备树节点扫描函数of_scan_flat_dt()。此函数被调用了三次,进行了chosen节点,root节点,memory操作。
void __init early_init_devtree(void *params)
{
initial_boot_params = params;
of_scan_flat_dt(early_init_dt_scan_chosen, arcs_cmdline);
of_scan_flat_dt(early_init_dt_scan_root, NULL);
of_scan_flat_dt(early_init_dt_scan_memory_arch, NULL);
}
early_init_dt_scan_chosen()
继续追踪第一个调用early_init_dt_scan_chosen函数实体,函数原型如下,函数路径见./drivers/of/fdt.c,我们可以看到对chosen节点内的bootargs参数做了解析处理,并不是chosen节点的所有参数,只是启动参数做了处理。
int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
int depth, void *data)
{
if (depth != 1 || !data ||
(strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
return 0;
.............
early_init_dt_check_for_initrd(node);
p = of_get_flat_dt_prop(node, "bootargs", &l);
.............
return 0;
}
early_init_dt_check_for_initrd()
继续向下追踪函数实体early_init_dt_check_for_initrd(),函数原型如下,函数路径见./drivers/of/fdt.c,我们可以看到对chosen节点内的linux,initrd机制参数做了解析处理。
void __init early_init_dt_check_for_initrd(unsigned long node)
{
prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);
start = of_read_ulong(prop, len/4);
prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);
end = of_read_ulong(prop, len/4);
early_init_dt_setup_initrd_arch(start, end);
}
early_init_dt_scan_root()
现在回到第二次of_scan_flat_dt()函数调用,追踪第二个函数实体early_init_dt_scan_root(),函数原型如下,函数路径见./drivers/of/fdt.c,我们可以看到对root节点内的#size-cells与#address-cells进行解析处理,赋值给全局变量,并不是对root节点的所有参数进行解析处理。
int __init early_init_dt_scan_root(unsigned long node, const char *uname,
int depth, void *data)
{
.............
dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
if (prop)
dt_root_size_cells = be32_to_cpup(prop);
prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
if (prop)
dt_root_addr_cells = be32_to_cpup(prop);
.............
}
early_init_dt_scan_memory_arch()
继续追踪第三个调用函数实体early_init_dt_scan_root(),函数原型如下,函数路径见./arch/mips/kernel/prom.c,此函数只是简单封装,我们不做过多介绍。
int __init early_init_dt_scan_memory_arch(unsigned long node,
const char *uname, int depth,
void *data)
{
return early_init_dt_scan_memory(node, uname, depth, data);
}
early_init_dt_scan_memory()
继续追踪函数实体early_init_dt_scan_memory(),函数原型如下,函数路径见./drivers/of/fdt.c,我们看到对memory节点device_type这个进行参数解析,name如果是memory节点,则去解析reg,解析到reg基地址与偏移长度,early_init_dt_add_memory_arch()申请内存空间,到此早期的设备树初始化结束,小结,主要是对chosn节点的内核启动参数bootargs,root节点#size-cells与#address-cells,memory节点内存空间解析处理,即设备树总体信息解析处理。
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
int depth, void *data)
{
char *type = of_get_flat_dt_prop(node, "device_type", NULL);
.................
reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
.................
early_init_dt_add_memory_arch(base, size);
.................
}
device_tree_init()
分析完成第一次设备树早期初始化函数plat_mem_setup(),接下来我们回到第二次设备树初始化函数device_tree_init()进行源代码追踪。可以看到device_tree_init()被第二次调用进行设备树初始化,函数原型如下,函数路径见./arch/mips/kernel/prom.c,第一步将dtb文件首地址转换成物理地址,获取dtb大小,进行内存初始化操作,预留一块内存区给设备用,防止内核启动时程序覆盖掉dtb文件地址,保护这块内容。第二步unflatten_device_tree()函数是实体就是真正的dtb文件转向device node解析接口函数。
void __init device_tree_init(void)
{
.................
.................
base = virt_to_phys((void *)initial_boot_params);
size = be32_to_cpu(initial_boot_params->totalsize);
reserve_mem_mach(base, size);
unflatten_device_tree();
free_mem_mach(base, size);
}
unflatten_device_tree()
继续追踪函数实体unflatten_device_tree(),函数原型如下,函数文件路径见
./drivers/of/fdt.c。第一步是对各个节点初始化转换,__unflatten_device_tree()这个函数有三个参数,第一个是dtb文件首地址,第二个参数是mynodes,等于of_node,第三个参数是申请的内存空间。第二步是通过查找chosen节点路径对节点进行操作,如果存在chosen节点就去解析chosen节点,否则,就去解析chosen@0。
void __init unflatten_device_tree(void)
{
__unflatten_device_tree(initial_boot_params, &allnodes,
early_init_dt_alloc_memory_arch);
of_chosen = of_find_node_by_path("/chosen");
if (of_chosen == NULL)
of_chosen = of_find_node_by_path("/chosen@0");
}
__unflatten_device_tree()
继续追踪函数实体__unflatten_device_tree(),函数原型如下,函数文件路径见./drivers/of/fdt.c,可以看出unflatten_dt_node()被调用了两次,第一次是申请device node的空间,第二次调用才是真正的解析。
static void __unflatten_device_tree(struct boot_param_header *blob,
struct device_node **mynodes,
void * (*dt_alloc)(u64 size, u64 align))
{
.........................
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);
.........................
start = ((unsigned long)blob) +
be32_to_cpu(blob->off_dt_struct);
unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0);
.........................
}
of_find_property()
经过层层调用,最终调到熟悉的设备解析接口proper = of_find_property()函数进行各种属性填充,函数原型如下,函数文件路径见./drivers/of/base.c,该函数的返回值struct property结构体定义见/include/linux/of.h,同时也是struct device_node的子结构体,结构体定义见/include/linux/of.h.
struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp)
{
...................
for (pp = np->properties; pp != 0; pp = pp->next) {
if (of_prop_cmp(pp->name, name) == 0) {
if (lenp != 0)
*lenp = pp->length;
break;
}
}
...................
}
至此,dtb文件经过一系列的调用转换成结构体struct device_node,后面函数就可以直接使用了。
原创文章,转载请注明此处!