设备树和Platform架构--3--kernel解析设备树(dtb----device_node)

参考:

https://blog.csdn.net/thisway_diy/article/details/84336817

https://blog.csdn.net/woyimibayi/article/details/77574736

1 概述

Linux使用设备树完成3个目的:

(1)识别machine(platform identification);

(2)运行信息提取与管理(runtime configuration);

(3)生成设备信息(device population);

 

2 kernel解析设备树的整体过程

 

函数调用过程

 

3 kernel解析Device Tree

3.1 解析前的准备

bootloader启动内核时,会设置r0,r1,r2三个寄存器

r0一般设置为0;
r1一般设置为machine id (在使用设备树时该参数没有被使用);
r2一般设置ATAGS或DTB的开始地址;

说明:

(1)machine id,是让内核知道是哪个CPU,从而调用对应的初始化函数。
以前没有使用设备树时,需要bootloader传一个machine id给内核,现在使用设备树的话,这个参数就不需要设置了。
(2)r2是以前的ATAGS开始地址,使用设备树后,是DTB文件开始地址。
 

uboot将一些参数,设备树文件传给内核,kernel主要完成一下过程:

内核head.S(arch/arm/kernel/head.S)所做工作如下:
(1)__lookup_processor_type : 使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息)
(2)__vet_atags : 判断是否存在可用的ATAGS或DTB
(3)__create_page_tables : 创建页表, 即创建虚拟地址和物理地址的映射关系
(4)__enable_mmu : 使能MMU, 以后就要使用虚拟地址了
(5) __mmap_switched(arch/arm/kernel/head-common.S) : 上述函数里将会调用__mmap_switched

  str r9, [r4]      @ Save processor ID
  str r1, [r5]      @ Save machine type
  str r2, [r6]      @ Save atags pointer

(6)把bootloader传入的r2参数, 保存到变量__atags_pointer
(7)调用C函数start_kernel

##最终效果
head.S和head-common.S最终效果:
把bootloader传来的r1值, 赋给了C变量: __machine_arch_type
把bootloader传来的r2值,赋给了C变量:__atags_pointer
 

3.2 选择machine_desc(platform identification)与运行信息提取管理(runtime configuration

这一小节包含2部分选择machine_desc(platform identification)与运行信息提取与管理(runtime configuration);

3.2.1 选择machine_desc(platform identification

在一个内核image中,可以支持多个单板,对于这些单板,都构造了一个machine_desc结构体。

对于JZ2440,它源自smdk2440,内核没有它的单独文件,它使用smdk2440的相关文件,代码。

(1)以前uboot使用ATAGS给内核传参数时,它会传入一个机器ID,内核会使用这个机器ID找到最合适的machine_desc。即机器ID与machine_desc里面的.nr比较,相等就表示找到了对应的machine_desc。

(2)使用DTB文件时,那么这时内核是如何选择对应的machine_desc呢?

model = "SMDK24440";
compatible = "samsung,smdk2440","samsung,smdk24140","samsung,smdk24xx";

compatible属性声明想要什么machine_desc,属性值可以是一系列字符串,依次与machine_desc匹配。
内核最好支持samsung,smdk2440,如果不支持,再尝试是否支持samsung,smdk24140,再不支持,最后尝试samsung,smdk24xx

总结:
    a. 设备树根节点的compatible属性列出了一系列的字符串, 表示它兼容的单板名,从"最兼容"到次之;

     b. 内核中有多个machine_desc,其中有dt_compat成员, 它指向一个字符串数组, 里面表示该machine_desc支持哪些单板;

     c. 使用compatile属性的值, 跟’’‘每一个machine_desc.dt_compat’’'比较,
               成绩为"吻合的compatile属性值的位置",
               成绩越低越匹配, 对应的machine_desc即被选中
 

3.2.2 运行信息提取管理(runtime configuration

对于运行信息的提取及管理,设备树只是起一个信息传递的作用,对这些信息配置的处理,也比较简单,即从设备树的DTB文件中,把这些设备信息提取出来赋给内核中的某个变量即可。

这一部分主要对三种类型的信息进行处理,分别是:
(1)/chosen节点中 bootargs属性;

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

(2)根节点的 #address-cells 和 #size-cells属性;

根节点的#address-cells和#size-cells属性指定属性参数的位数,比如指定前面memory中的reg属性的地址是32位还是64位,大小是用一个32位表示,还是两个32位表示。
(3)/memory中的 reg属性;

./memory中的reg属性指定了不同板子内存的大小和起始地址。

总结:
    a. /chosen节点中bootargs属性的值, 存入全局变量: boot_command_line
    b. 确定根节点的这2个属性的值: #address-cells, #size-cells
         存入全局变量: dt_root_addr_cells, dt_root_size_cells
    c. 解析/memory中的reg属性, 提取出"base, size", 最终调用memblock_add(base, size);
 

3.2.3  源码结构

以arm为例进行分析,arm64上有一些差异

内核解析dtb文件并匹配单板,对设备树中运行时配置信息的处理(主要获取三个信息:bootargs信息、#address-cells和 #size-cells 、内存信息)

start_kernel()    //  arch/arm/kernel/head.S
    >>>setup_arch(&command_line);    //   arch/arm/kernel/setup.c
            >>> mdesc = setup_machine_fdt(__atags_pointer);
                #uboot以dtb形式传递给kernel,__fdt_pointer(即r2)指向dtb在内存中的首地址(虚拟地址);最后返回的是machine_desc;在函数中还完成了运行信息的提取动作,见下面
                    >>>early_init_dt_verify(phys_to_virt(dt_phys))// 验证dtb头部是否有效  kernel/drivers/of/fdt.c
                    >>> mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
                            #(1)在设备匹配表中匹配最合适的machine
                            >>>dt_root = of_get_flat_dt_root();
                            >>>  while ((data = get_next_compat(&compat))) {
                                        score = of_flat_dt_match(dt_root, compat);
                                        if (score > 0 && score < best_score) {
                                          best_data = data;
                                          best_score = score;
                                        }    
                                     }
                    >>>early_init_dt_scan_nodes();
                            #(2)解析设备树中的运行时信息
                            >>> 从/chosen node节点中提取信息command line                                          
                                     of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
                            >>> 初始化{size,address}-cells信息
                                    of_scan_flat_dt(early_init_dt_scan_root, NULL);
                             >>> 设置memory
                                   of_scan_flat_dt(early_init_dt_scan_memory, NULL);
           >>> machine_desc = mdesc;     #最后将选择出来 machine_desc
                    machine_name = mdesc->name;

 

3.3 生成设备信息(device population

这一部分主要讲解dtb------->struct device_node的过程。struct device_node---->platform_device后面讲解

Device Tree中的每一个node节点经过kernel处理都会生成一个struct device_node的结构体,struct device_node最终一般会被挂接到具体的struct device结构体。

dtb转换为device_node(unflatten)
start_kernel()    //  arch/arm/kernel/head.S
    >>>setup_arch(&command_line);    //   init/main.c
            >>> mdesc = setup_machine_fdt(__atags_pointer);
            >>> machine_desc = mdesc;
            >>> machine_name = mdesc->name;
            >>>arm_memblock_init(mdesc);#为相应的资源预留内存 
                    >>>early_init_fdt_reserve_self();#预留dtb本身占用的内存大小
                    >>>early_init_fdt_scan_reserved_mem();#fdt中要求保留的内存大小
            >>>unflatten_device_tree();#根据dtb创建  struct device_node 树型链表
                    >>>__unflatten_device_tree(initial_boot_params, NULL, &of_root,
                                                                 early_init_dt_alloc_memory_arch, false);
                            #还原设备树,创建struct device_node链表
                            >>>size = unflatten_dt_nodes(blob, NULL, dad, NULL);
                                    #第一次unflatten,获得dtb所需device_node大小
                                    
                            >>>mem = dt_alloc(size + 4, __alignof__(struct device_node));
                                    #为设备树device_node分配内存
                            >>>  unflatten_dt_nodes(blob, mem, dad, mynodes);                        
                                    #第二次unflatten,创建device_node,组成device_node链表,还原设备树
                                   
                                   
                    >>>of_alias_scan(early_init_dt_alloc_memory_arch);
                            #获取chosen及aliases节点指针方便后续使用
                    >>>unittest_unflatten_overlay_base();
                    
                    
                    
 其中,unflatten_dt_nodes()函数调用了
 fpsizes[depth+1] = populate_node(blob, offset, &mem, nps[depth], 
                                    fpsizes[depth],  &nps[depth+1], dryrun);
        >>>np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
                                   __alignof__(struct device_node));//struct device_node分配内存
        >>>初始化struct device_node成员
                >>>of_node_init(np);   //
                >>> np->full_name = fn = ((char *)np) + sizeof(*np);
                >>> if (dad != NULL) {                                                                                
                          np->parent = dad;
                          np->sibling = dad->child;
                          dad->child = np;
                        }

struct device_node *of_root是所有struct device_node的根节点

 

为了防止内核启动时,占用dtb及dtb要求的memreserve空间,在启动初期,会预留出相应的内存空间。可以看到,先把dtb中的memreserve信息告诉内核,把这块内存区域保留下来,不占用它。

然后将扁平结构的设备树提取出来,构造成一个树,这里涉及两个结构体:device_node结构体和property结构体。

(1)在dts文件里,每个大括号{ }代表一个节点,比如根节点里有个大括号,对应一个device_node结构体;memory也有一个大括号,也对应一个device_node结构体。
(2)节点里面有各种属性,也可能里面还有子节点,所以它们还有一些父子关系。
(3)根节点下的memory、chosen、led等节点是并列关系,兄弟关系。
(4)对于父子关系、兄弟关系,在device_node结构体里面肯定有成员来描述这些关系。
 

struct device_node结构体描述如下:

 

struct device_node  {

    const  char *name;              /* node的名称,取最后一次“/”和“@”之间子串 */

    const  char *type;              /* device_type的属性名称,没有为 */

    phandle  phandle;               /* phandle属性值 */

    const  char *full_name;        /*指向该结构体结束的位置,存放node的路径全名,例如:/chosen */

    struct  fwnode_handle fwnode;

 

    struct property *properties;  /*指向该节点下的第一个属性,其他属性与该属性链表相接 */

    struct property *deadprops;   /* removed properties */

    struct device_node *parent;   /*父节点 */

    struct device_node *child;    /*子节点 */

    struct device_node *sibling;  /*姊妹节点,与自己同等级的node */

    struct kobject kobj;            /* sysfs文件系统目录体现 */

    unsigned  long _flags;          /*当前node状态标志位,见/include/linux/of.h line124-127 */

    void   *data;

};

 

/* flag descriptions (need to be  visible even when !CONFIG_OF) */

#define OF_DYNAMIC        1 /* node and properties were  allocated via kmalloc */

#define OF_DETACHED       2  /* node has been detached from the device tree*/

#define OF_POPULATED      3  /* device already created for the node */

#define OF_POPULATED_BUS  4 /* of_platform_populate recursed to children of this node */

 

struct property结构体描述如下:

 

struct property  {

    char  *name;                          /* property full name */

    int  length;                          /* property  value length */

    void  *value;                         /* property  value */

    struct  property *next;             /* next property  under the same node */

    unsigned  long _flags;

    unsigned  int unique_id;

    struct  bin_attribute attr;        /*属性文件,与sysfs文件系统挂接 */

};

 

两个结构体与dts内容的对于关系如下:

总的来说,kernel根据Device Tree的文件结构信息转换成struct property结构体,并将同一个node节点下面的所有属性通过property.next指针进行链接,形成一个单链表。

下面具体分析以上信息是如何得来的。Device Tree的解析首先从unflatten_device_tree()开始,然后调用函数__unflatten_device_tree();

(1)参数initial_boot_params指向Device Tree在内存中的首地址;

(2)of_root在经过该函数处理之后,会指向根节点;

(3)early_init_dt_alloc_memory_arch是一个函数指针,为struct device_node和struct property结构体分配内存的回调函数(callback);

在__unflatten_device_tree()函数中,两次调用unflatten_dt_node()函数,第一次是为了得到Device Tree转换成struct device_node和struct property结构体需要分配的内存大小;第二次调用才是具体填充每一个struct device_node和struct property结构体。


unflatten_dt_node()代码列出如下:

 

/**

 * unflatten_dt_node - Alloc and populate a  device_node from the flat tree

 * @blob: The parent device tree blob

 * @mem: Memory chunk to use for allocating  device nodes and properties

 * @poffset: pointer to node in flat tree

 * @dad: Parent struct device_node

 * @nodepp: The device_node tree created by  the call

 * @fpsize: Size of the node path up at the  current depth.

 * @dryrun: If true, do not allocate device  nodes but still calculate needed

 * memory size

 */

static void * unflatten_dt_node(const  void *blob,

              void  *mem,

              int  *poffset,

              struct  device_node *dad,

              struct  device_node **nodepp,

              unsigned  long fpsize,

              bool  dryrun)

{

    const  __be32 *p;

    struct  device_node *np;

    struct  property *pp, **prev_pp = NULL;

    const  char *pathp;

    unsigned  int l, allocl;

    static  int depth;

    int  old_depth;

    int  offset;

    int  has_name = 0;

    int  new_format = 0;

 

    /*  获取node节点的name指针到pathp中 */

    pathp  = fdt_get_name(blob, *poffset, &l);

    if  (!pathp)

       return  mem;

 

    allocl  = ++l;

 

    /*  version 0x10 has a more compact unit name here instead of the full

     * path. we accumulate the full path size  using "fpsize", we'll rebuild

     * it later. We detect this because the first  character of the name is

     * not '/'.

     */

    if  ((*pathp) != '/') {

       new_format  = 1;

       if  (fpsize == 0) {

           /*  root node: special case. fpsize accounts for path

            * plus terminating zero. root node only has  '/', so

            * fpsize should be 2, but we want to avoid  the first

            * level nodes to have two '/' so we use  fpsize 1 here

            */

           fpsize  = 1;

           allocl  = 2;

           l  = 1;

           pathp  = "";

       }  else {

           /*  account for '/' and path size minus terminal 0

            * already in 'l'

            */

           fpsize  += l;

           allocl  = fpsize;

       }

    }

 

    /*  分配struct device_node内存,包括路径全称大小 */

    np  = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,

              __alignof__(struct  device_node));

    if  (!dryrun) {

       char  *fn;

       of_node_init(np);

 

       /*  填充full_name,full_name指向该node节点的全路径名称字符串 */

       np->full_name  = fn = ((char *)np) + sizeof(*np);

       if  (new_format) {

           /*  rebuild full path for new format */

           if  (dad && dad->parent) {

              strcpy(fn,  dad->full_name);

              fn  += strlen(fn);

           }

           *(fn++)  = '/';

       }

       memcpy(fn,  pathp, l);

 

       /*  节点挂接到相应的父节点、子节点和姊妹节点 */

       prev_pp  = &np->properties;

       if  (dad != NULL) {

           np->parent  = dad;

           np->sibling  = dad->child;

           dad->child  = np;

       }

    }

    /*  处理该node节点下面所有的property */

    for  (offset = fdt_first_property_offset(blob, *poffset);

         (offset >= 0);

         (offset = fdt_next_property_offset(blob,  offset))) {

       const  char *pname;

       u32  sz;

 

       if  (!(p = fdt_getprop_by_offset(blob, offset, &pname, &sz))) {

           offset  = -FDT_ERR_INTERNAL;

           break;

       }

 

       if  (pname == NULL) {

           pr_info("Can't  find property name in list !\n");

           break;

       }

       if  (strcmp(pname, "name") == 0)

           has_name  = 1;

       pp  = unflatten_dt_alloc(&mem, sizeof(struct property),

                  __alignof__(struct  property));

       if  (!dryrun) {

           /*  We accept flattened tree phandles either in

            * ePAPR-style "phandle"  properties, or the

            * legacy "linux,phandle"  properties.  If both

            * appear and have different values, things

            * will get weird.  Don't do that. */

          

           /*  处理phandle,得到phandle值 */

           if  ((strcmp(pname, "phandle") == 0) ||

               (strcmp(pname, "linux,phandle")  == 0)) {

              if  (np->phandle == 0)

                  np->phandle  = be32_to_cpup(p);

           }

           /*  And we process the "ibm,phandle" property

            * used in pSeries dynamic device tree

            * stuff */

           if  (strcmp(pname, "ibm,phandle") == 0)

              np->phandle  = be32_to_cpup(p);

           pp->name  = (char *)pname;

           pp->length  = sz;

           pp->value  = (__be32 *)p;

           *prev_pp  = pp;

           prev_pp  = &pp->next;

       }

    }

    /*  with version 0x10 we may not have the name property, recreate

     * it here from the unit name if absent

     */

    /*  为每个node节点添加一个name的属性 */

    if  (!has_name) {

       const  char *p1 = pathp, *ps = pathp, *pa = NULL;

       int  sz;

 

       /*  属性name的value值为node节点的名称,取“/”和“@”之间的子串 */

       while  (*p1) {

           if  ((*p1) == '@')

              pa  = p1;

           if  ((*p1) == '/')

              ps  = p1 + 1;

           p1++;

       }

       if  (pa < ps)

           pa  = p1;

       sz  = (pa - ps) + 1;

       pp  = unflatten_dt_alloc(&mem, sizeof(struct property) + sz,

                  __alignof__(struct  property));

       if  (!dryrun) {

           pp->name  = "name";

           pp->length  = sz;

           pp->value  = pp + 1;

           *prev_pp  = pp;

           prev_pp  = &pp->next;

           memcpy(pp->value,  ps, sz - 1);

           ((char  *)pp->value)[sz - 1] = 0;

       }

    }

    /*  填充device_node结构体中的name和type成员 */

    if  (!dryrun) {

       *prev_pp  = NULL;

       np->name  = of_get_property(np, "name", NULL);

       np->type  = of_get_property(np, "device_type", NULL);

 

       if  (!np->name)

           np->name  = "";

       if  (!np->type)

           np->type  = "";

    }

 

    old_depth  = depth;

    *poffset  = fdt_next_node(blob, *poffset, &depth);

    if  (depth < 0)

       depth  = 0;

    /*  递归调用node节点下面的子节点 */

    while  (*poffset > 0 && depth > old_depth)

       mem  = unflatten_dt_node(blob, mem, poffset, np, NULL,

                  fpsize,  dryrun);

 

    if  (*poffset < 0 && *poffset != -FDT_ERR_NOTFOUND)

       pr_err("unflatten:  error %d processing FDT\n", *poffset);

 

    /*

     * Reverse the child list. Some drivers  assumes node order matches .dts

     * node order

     */

    if  (!dryrun && np->child) {

       struct  device_node *child = np->child;

       np->child  = NULL;

       while  (child) {

           struct  device_node *next = child->sibling;

           child->sibling  = np->child;

           np->child  = child;

           child  = next;

       }

    }

 

    if  (nodepp)

       *nodepp  = np;

 

    return  mem;

}

 

通过以上函数处理就得到了所有的struct device_node结构体,为每一个node都会自动添加一个名称为“name”的property,property.length的值为当前node的名称取最后一个“/”和“@”之间的子串(包括‘\0’)。例如:/serial@e2900800,则length = 7,property.value = device_node.name = “serial”。

 

你可能感兴趣的:(嵌入式,Kernel)