基于Mips架构linux下设备树解析(二)

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,后面函数就可以直接使用了。
原创文章,转载请注明此处!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值