kernel解析dtb为节点


title: 解析dtb为节点
date: 2019/4/26 14:02:18
toc: true
---

kernel解析dtb为节点

head.s入口传递

回顾

看以前的笔记 kernel(二)源码浅析

先来回顾下以前uboot是怎么传递参数的?

R0一般设置为0
R1machine id (设备树不使用)
R2ATAGS(设备树使用为DTB地址)

kernel的入口点是`arch\arm\kernel\head.S,以前的流程是根据这个machine id去匹配到具体的单板,然后使用ATAGS构造相应的启动参数

使用机器id可以找到类似如下的结构体

#define MACHINE_START(_type,_name)          \
static const struct machine_desc __mach_desc_##_type    \
 __used                         \
 __attribute__((__section__(".arch.info.init"))) = {    \
    .nr     = MACH_TYPE_##_type,        \
    .name       = _name,

#define MACHINE_END             \
};

MACHINE_START(S3C2440, "SMDK2440")
    /* Maintainer: Ben Dooks <ben@fluff.org> */
    .phys_io    = S3C2410_PA_UART,
    .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    .boot_params    = S3C2410_SDRAM_PA + 0x100,

    .init_irq   = s3c24xx_init_irq,
    .map_io     = smdk2440_map_io,
    .init_machine   = smdk2440_machine_init,
    .timer      = &s3c24xx_timer,
MACHINE_END

具体这个结构如下

 struct machine_desc {
    /*
     * Note! The first four elements are used
     * by assembler code in head-armv.S
     */
    unsigned int        nr;     /* architecture number  */
    unsigned int        phys_io;    /* start of physical io */
    unsigned int        io_pg_offst;    /* byte offset for io 
                         * page tabe entry  */

    const char      *name;      /* architecture name    */
    unsigned long       boot_params;    /* tagged list      */

    unsigned int        video_start;    /* start of video RAM   */
    unsigned int        video_end;  /* end of video RAM */

    unsigned int        reserve_lp0 :1; /* never has lp0    */
    unsigned int        reserve_lp1 :1; /* never has lp1    */
    unsigned int        reserve_lp2 :1; /* never has lp2    */
    unsigned int        soft_reboot :1; /* soft reboot      */
    void            (*fixup)(struct machine_desc *,
                     struct tag *, char **,
                     struct meminfo *);
    void            (*map_io)(void);/* IO mapping function  */
    void            (*init_irq)(void);
    struct sys_timer    *timer;     /* system tick timer    */
    void            (*init_machine)(void);
};

设备树启动浅析

head.s会调用head-common.S

a. __lookup_processor_type : 使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息)
b. __vet_atags  : 判断是否存在可用的ATAGS或DTB
c. __create_page_tables  : 创建页表, 即创建虚拟地址和物理地址的映射关系
d. __enable_mmu  : 使能MMU, 以后就要使用虚拟地址了
e. __mmap_switched  : 上述函数里将会调用__mmap_switched
    .long   __bss_start         @ r0
    .long   __bss_stop          @ r1
    .long   init_thread_union + THREAD_START_SP @ sp

    .long   processor_id            @ r0
    .long   __machine_arch_type     @ r1
    .long   __atags_pointer         @ r2
f. 把bootloader传入的r2参数, 保存到变量__atags_pointer中
g. 调用C函数start_kernel

start_kernel

先记住这个

    .long   processor_id            @ r0
    .long   __machine_arch_type     @ r1
    .long   __atags_pointer         @ r2

大概的流程是这样的

mdesc = setup_machine_fdt(__atags_pointer);
    // 头部检查
    early_init_dt_verify
    //找到最匹配的machine_desc
    of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

if (!mdesc) //按照以前使用atag的方式
    mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);

// 找到匹配的machine_desc,后处理了   
machine_desc = mdesc;
machine_name = mdesc->name;
dump_stack_set_arch_desc("%s", mdesc->name);
// 把命令行启动参数存起来
/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;

// 保留dtb 本身的内存 以及指定的 reserve的内存
arm_memblock_init(mdesc);

启动参数以及内存解析

  • /chosen节点中bootargs属性就是内核启动的命令行参数,它里面可以指定根文件系统在哪里,第一个运行的应用程序是哪一个,指定内核的打印信息从哪个设备里打印出来,存在boot_command_line

  • /memory中的reg属性指定了不同板子内存的大小和起始地址,调用memblock_add

  • 根节点的#address-cells和#size-cells属性指定属性参数的位数

代码浅析

setup_machine_fdt

    mdesc_best = &__mach_desc_GENERIC_DT;  这个应该是机器描述符段的起始地址

    early_init_dt_verify
        // 头部校验
        fdt_check_header
        // 把这个 地址又存一遍 initial_boot_params
        initial_boot_params = params;
        //计算crc到 of_fdt_crc32
        of_fdt_crc32=crc32_be(...)
    mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
        // arch_get_next_mach 获取下一个机器描述
        
        // 寻找下一个机器描述的id 
        // get_next_compat=arch_get_next_mach
        // 最终找到最匹配的机器描述
        while ((data = get_next_compat(&compat))) 
        {
            score = of_flat_dt_match(dt_root, compat);
                    // initial_boot_params 就是上面存档的dtb地址
                    of_fdt_match(initial_boot_params, node, compat);
                        of_fdt_is_compatible
                            // 寻找  compatible 属性来匹配 单板
                            fdt_getprop(blob, node, "compatible", &cplen);
                            // 找到最匹配的
                            while (cplen > 0)
                                of_compat_cmp
                    
            if (score > 0 && score < best_score)
            {
                best_data = data;
                best_score = score;
            }
        }

    early_init_dt_scan_nodes    
        // 找到启动命令参数
        /* Retrieve various information from the /chosen node */
        of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
            early_init_dt_scan_chosen // 寻找到 chosen 的 bootargs
                p = of_get_flat_dt_prop(node, "bootargs", &l);
        // 找到reg的描述 
        /* Initialize {size,address}-cells info */
        of_scan_flat_dt(early_init_dt_scan_root, NULL);
            prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
            prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
        // 内存相关设置
        /* Setup memory, calling early_init_dt_add_memory_arch */
        of_scan_flat_dt(early_init_dt_scan_memory, NULL);
            early_init_dt_scan_memory
                if( ! of_get_flat_dt_prop(node, "linux,usable-memory", &l);)
                else of_get_flat_dt_prop(node, "reg", &l);
                ....
            //  
            of_get_flat_dt_prop(node, "hotpluggable", NULL);

内存保留

uboot把设备树DTB文件随便放到内存的某一个地方就可以使用,内核不会去覆盖DTB所占用的那块内存呢.在设备树文件中,可以使用/memreserve/指定一块内存,这块内存就是保留的内存,内核不会占用它。即使你没有指定这块内存,当我们内核启动时,他也会把设备树所占用的区域保留下来.

setup_arch
    // dtb 判断,机器id查找
    setup_machine_fdt
    
    // 内存保留
    arm_memblock_init(mdesc);
        // dtb 自身的内存空间
        early_init_fdt_reserve_self();
            early_init_dt_reserve_memory_arch(
                         __pa(initial_boot_params),             //initial_boot_params 是以前存储的dtb地址
                          fdt_totalsize(initial_boot_params),   //头部指示的大小
                          0);
                memblock_reserve(....)
        // dtb 描述中的 reserve 内存
        early_init_fdt_scan_reserved_mem();
            fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
                early_init_dt_reserve_memory_arch
                    memblock_reserve(....)

小结

综上,我们到此为止解析了如下

chosen/bootargs启动命令行boot_command_line
memory保留内存
dtb本身内存
initial_boot_paramsdtb地址,这个是个全局变量

节点的解析

这段代码的入口是这里

setup_arch
    // 找到匹配的机器描述
    setup_machine_fdt
    // 内存保留
    arm_memblock_init(mdesc);
    // 节点解析
    unflatten_device_tree();

unflatten_device_tree()
{
    // 第一次解析,这里最后一个参数是false,不会分配内存,会计算所需的内存大小
    __unflatten_device_tree(initial_boot_params, NULL, &of_root,
                early_init_dt_alloc_memory_arch, false);    
    
    /* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
    of_alias_scan(early_init_dt_alloc_memory_arch);

    // 这里会真正分配内存,存放结构
    unittest_unflatten_overlay_base();
    
}

这段代码还是比较复杂了,暂时不去分析了这里没有使用递归,按我的理解应该是类似这样的

for(next_node(xxx))-----这里的next_node 会记录树的深度,也就是应该会有父兄的记录
{
    // 解析status
    
    // 解析属性
    if(is_not_属性)
        continue;

}

数据结构

节点描述

struct device_node {
    const char *name;  // 来自节点中的name属性, 如果没有该属性, 则设为"NULL"
    const char *type;  // 来自节点中的device_type属性, 如果没有该属性, 则设为"NULL"
    phandle phandle;
    const char *full_name;  // 节点的名字, node-name[@unit-address]
    struct fwnode_handle fwnode;

    struct  property *properties;  // 节点的属性
    struct  property *deadprops;    /* removed properties */
    struct  device_node *parent;   // 节点的父亲
    struct  device_node *child;    // 节点的孩子(子节点)
    struct  device_node *sibling;  // 节点的兄弟(同级节点)
    #if defined(CONFIG_OF_KOBJ)
    struct  kobject kobj;
    #endif
    unsigned long _flags;
    void    *data;
    #if defined(CONFIG_SPARC)
    const char *path_component_name;
    unsigned int unique_id;
    struct of_irq_controller *irq_trans;
    #endif
};

属性描述

struct property {
    char    *name;    // 属性名字, 指向dtb文件中的字符串
    int length;       // 属性值的长度
    void    *value;   // 属性值, 指向dtb文件中value所在位置, 数据仍以big endian存储
    struct property *next;
    #if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
    unsigned long _flags;
    #endif
    #if defined(CONFIG_OF_PROMTREE)
    unsigned int unique_id;
    #endif
    #if defined(CONFIG_OF_KOBJ)
    struct bin_attribute attr;
    #endif
};

具体的描述看下老师的图,很容易理解,就是一个比较大的链表,注意其中节点名字指向节点本身最后的内存

mark

转载于:https://www.cnblogs.com/zongzi10010/p/10793075.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值