3.3.1. 内核对设备树的支持

从源头上分析,uboot将一些参数,设备树文件传递给内核。内核将如何处理设备树文件呢

从内核的第一个执行文件head.S开始分析

  • R0 R1 R2三个寄存器的设置

    uboot启动内核时会设置r0 r1 r2三个寄存器 r0 一般设置为0 r1 一般设置为machine id(在使用设备树时该参数没有被使用) r2 一般设置ATAGS或者dtb的开始地址

    这里的machine id是让内核知道时那个CPU,从而调用对应的初始化函数。 以前没有设备树的时候需要传递这个参数,有设备树后这个参数就不再需要设置了

  • head.S的内容

    内核head.S所作的工作如下: a) __lookup_processor_type 使用汇编指令读取CPU ID根据该id找到对应的proc_info_list结构体(里面包含这类CPU的初始化函数、信息) b) __vet_atags 判断是否有可用的dtb c) __create_page_tables 创建页表,即创建虚拟地址和物理地址的映射关系 d) __enable_mmu 使能MMC,以后就要使用虚拟地址了 e) 将uboot传入的R2参数,保存到__atags_pointer中 f) 调用C函数start_kernel

3.3.1.1. 对设备树中平台信息的处理(选择machine_desc)

  • 内核是如何选择对应的machine_desc

    一个编译为image的内核镜像文件,可以支持多个单板,这些板子的配置少有不同,需要做一些单独的初始化。 内核中针对这些单板,构造了一个machine_desc结构体,里面有.init和.nr。 设备树的根节点里,有如下两行

    model="SMDK2440";
    compatible="samsung,smdk2440","samsung,smdk2410","samsung,smdk24xx";
    

    这里的compatible属性声明想要什么machine_desc,属性值可以是一些列字符串,依次与machine_desc匹配。 内核中有多个machine_desc,其中有个dt_compat成员,它指向一个字符串数组,里面表示该machine_desc支持哪些单板.

  • start_kernel的调用过程

    head.s会把dtb的位置保存在变量__atags_pointer里,最后调用start_kernel。 start_kernel 的调用过程如下

    start_kernel  //init/main.c
        setup_arch(&command_line); //arch/arm/kernel/setup.c
            mdest = setup_machine_fdt(__atags_pointer);//arch/arm/kernel/setup.c
                early_init_dt_verify(phys_to_virt(dt_phys))  //判断是否有效的dtb,drivers/of/fdt.c                    initial_boot_params = params;
                mdesc = of_flat_dt_match_machine(mdesc_best,arch_get_next_mach);//找到最匹配的machine_desc。 /drivers/of/ftd.c
                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;
                    }
                }
            machine_desc = mdesc;
    
  • 对设备树中运行时配置信息的处理

    设备树只是其一个信息传递的作用,对这些信息配置的处理,也比较简单,即从设备树的dtb文件中,把这些设备鄂信息提取出来赋值给内核的某个变量即可。 函数调用过程如下

    start_kernel //init/main.c
        setup_arch(&command_line); //arch/arm/kernel/setup.c
            mdesc = setup_machine_fdt(__atags_pointer); //arch/arm/kernel/devtree.c
                early_init_dt_scan_nodes(); //arch/arm/kernel/devtree.c
                //从chosen 节点中检索信息
                of_scan_flat_dt(early_init_dt_scan_chosen,boot_command_line);
                // 初始化 {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);
    

里面主要对三种类型的信息进行处理,分别是 /chosen 节点中bootargs属性,根节点的#address-cells和#size-cells属性,/memory的reg属性

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

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

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

总结: * 将/chosen节点中的bootargs属性的值存入全局变量boot_command_line * 确定根结点的两个属性的值#address-cells, #size-cells 分别存入全局变量 dt_root_addr_cells dt_root_size_cells * 解析/memory中reg属性。提取出base size,最终调用memblock_add(base,size);

3.3.1.2. dtb转换为device_node

在dts文件里,每一个大括号{}代表一个节点,比如根结点里有个大括号,对应一个device_node结构体.memory也有一个大括号,也对应一个device_node结构体。 节点中有各种属性,还有子节点,所以存在父子关系或者兄弟关系。 include/linux/of.h 中有对device_node结构体的定义:

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 *siblingl;   //兄弟节点
    #if defined(CONFIG_OF_KOBJ)
        struct kobject kobj;
    #endif
    unsigned long _flags;
    void *data;
    #if defined(CONFIG_SPACE)
        const char *path_component_name;
        unsigned int unique_id;
        struct of_irq_controller *irq_trans;
    #endif
}

device_node结构体表示一个节点,property结构体表示节点的具体属性。

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

两个结构体与dts内容的对应关系如下图所示:

../../../_images/Ldd_devicetree_chapter3_4_001.jpg

3.3.1.3. device_node 转换为platfom_device

  • 哪些device_node可以转换platform_device

/{
    model = "SMDK2440";
    compatible = "samsung,smdk2440";

    #address-cells = <1>;
    #size-cells = <1>;

    memory@0x30000000{
        device_type = "memory";
        reg = <0x30000000 0x4000000>;
    };
    chosen{
        bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
    };
    //只有len设备才会转换成platform_device
    led{
        compatible = "jz2440_led";
        reg = <S3C24C10_GPF(5) 1>;
    };
 };
  1. 内核函数of_platform_default_populate_init,遍历device_node树,生成platform_device。

  2. 并非所有的device_node都会转换成platform_device,只有以下的device_node会转换 a) 该节点必须含有compatible属性 b) 根节点的子节点 c) 含有特殊compatible属性的节点的子节点(子节点必须含有compatible属性)。

    ##这些特殊的compatible属性为:
    "simple-bus","simple-mfd","isa","arm,amba-bus"
    

根节点时例外的,生成platform_device时,即使有compatible属性也不会处理

  • 如何在设备树中描述硬件

    示例:

/{
    mytest{
        compatible = "mytest","sample-bus";
        mytest@0{
            compatible = "mytest_0";
        };
    };
    i2c{
        compatible = "samsung,i2c";
        at24c02{
            compatible = "at24c02";
        };
    };
    spi{
        compatible = "samsung,spi";
        flash@0{
            compatible = "winbond,w25q32dw";
            spi-max-frequency = <25000000>;
            reg = <0>;
        };
    };
};
  1. 以上示例中,/mytest会被转换为platform_device,因为他兼容”sample-bus”,它的子节点/mytest/mytest@0 也会被转换为platform_device

  2. /i2c节点一般表示i2c控制器,它会被转换为platform_device,在内核中有对应的platform_driver。

    /i2c/at24c02节点不会被转换为platform_device,它被如何处理完全由父节点的platform_driver决定。一般是被创建为一个i2c_client

  3. /spi节点一般表示spi控制器,它会被转换为platform_device,在内核中有对应的platform_driver。

    /spi/flash@0节点不会被转换为platform_device,它被如何处理完全由父节点的platform_driver决定。一般是被创建为一个spi_client

  • 转换过程

  1. 入口函数 of_platform_default_populate_init,生成platform_device //drivers/of/platform.c

of_platform_default_populate_init
    of_platform_default_populate(NULL,NULL,NULL);
        of_platform_populate(NULL,of_default_bus_match_table,NULL,NULL)
        for_each_child_of_node(root,child){
            rc=of_platform_bus_create(child,matches,lookup,patent,true);
            dev=of_device_alloc(np,bus_id,patent);  //根据device_node节点的属性设置platform_device的resource
            if(rc){
                of_node_put(child);
                break;
            }
        }
  1. of_platform_bus_create(bus,matches,..)调用过程,处理bus节点生成platform_device,并决定是否处理他的子节点

dev = of_platform_device_create_pdata(bus,bus_id,platform_data,patent); //生成bus节点的platform_device结构体
if(!dev || !of_match_node(matches,bus))  //如果bus节点的compatible属性不吻合matches表就部处理它的子节点
    return 0;

    for_each_child_of_node(bus,child){  //遍历子节点
        pr_debug("create child:%pOF\n",child);
        rc = of_platform_bus_create(child,matches,lookuo,&dev->dev,strict);     //处理它的子节点,of_platform_bus_create是一个递归调用
        if(rc){
            of_node_put(child);
            break;
        }
    }
  1. i2c节点一般表示i2c控制器,它会被转换为platform_device,在内核中有对应的platform_driver,platform_driver的probe函数中会调用i2c_add_numbered_adapter:

i2c_add_numbered_adapter    //drivers/i2c/i2c-core-base.c
    __i2c_add_numbered_adapter
        i2c_register_adapter
            of_i2c_register_devices(adap); //drivers/i2c/i2c-core-of.c
            for_each_available_child_of_node(bus,node){
                client = of_i2c_register_device(adap,node);
                    client = i2c_new_device(adap.&info);    //设备树中的i2c子节点被转换为i2c_client
            }

3.3.1.4. platform_device与platform_driver匹配

  • 注册platform_driver的过程

platform_driver_register
    __platform_driver_register
        drv->driver.prober = platform_drv_prober;
        driver_register
            bus_add_driver
            klist_add_tail(&priv->knode_bus,&bus->p->klist_drivers);    //把platform_driver放入platform_bus_type的driver链表中
            driver_attach
            bus_for_each_dev(drv->bus,NULL,drv,__driver_attach);    //对于platform_bus_type的每一个设备,调用_driver_attach
            __driver_attach
                ret=driver_match_device(drv,dev);   //判断dev和drv是否匹配成功
                    return drv->bus->match ? drv->bus->match(dev,drv) : 1;  //调用platform_bus_type.match
                driver_probe_device(drv,dev);
                    really_probe
                        drv->probe  //platform_drv_probe
                         platform_drv_probe
                          struct platform_driver *drv = to_platform_driver(_dev->driver);
                          drv->probe
  • 注册platform_device的过程

pltform_device_register
    platform_device_add
        device_add
            bus_add_device
                klist_add_tail(&dev->p->knode_bus,&bus->p->klist_devices);      //把platform_device放入platform_bus_type的device链表中
            bus_probe_device(dev)
             device_initial_prober
               __device_attach
               ret = bus_for_each_drv(dev->bus,NULL,&data,__device_attach_driver);  //对于platform_bus_type下的每一个driver,调用__device_attach_driver
               __device_attach_driver
                 ret=driver_match_device(drv,dev);
                 return drv->bus->match ? drv->bus->match(dev,drv) : 1;     //调用platform_bus_type.match
                 driver_probe_device

匹配函数是platform_bus_type.match ,即platform_match,匹配过程按优先顺序罗列如下

  1. 比较platform_dev.driver_override和platform_driver.drv->name

  2. 比较platform_dev.dev.of_node的compatible属性和platform_driver.drv->of_match_table

  3. 比较platform_dev.name和platform_driver.id_table

  4. 比较platform_dev.name和platform_driver.drv->name

有一个匹配成功,即匹配成功

3.3.1.5. 内核中设备树操作函数

include/linux 目录下有很多of开头的头文件,dtb—->device_node—–>platform_device

  • 处理dtb

of_fdt.h        //dtb文件的相关操作函数,我们一般用不到,因为dtb文件在内核中已经转换为device_node树(它更易于使用)
  • 处理device_node

of.h                //提供设备树的一般处理函数,比如of_property_read_u32(读某个属性的u32值),*of_get_child_count(获取某个device_node的子节点数)
of_address.h        //地址相关的函数,,比如of_get_address(获取reg属性中的addr,size值)
                        of_match_device(从matches数组中取出与当前设备最匹配的一项)
of_dma.h            //设备树中DMA相关属性的函数
of_gpio.h           //GPIO相关的函数
of_graph.h          //GPU相关驱动中用到的函数,从设备中获取GPU信息
of_iommu.h          //很少用到
of_irq.h            //中断相关的函数
of_mdio.h           //MDIO(Ethernet PHY)api
of_net.h            //
of_pci.h            //PCI相关函数
of_pdt.h            //很少用到
of_reserved_mem.h   //reserved_mem相关函数

以中断作为例子,一个设备可以发出中断,必须包含中断号和中断触发方式. 官方设备树规格书里面的设备示例

soc{
    #address-cells = <1>;
    #size-cells = <1>;

    serial{
        compatible = "ns16550";
        reg = <0x4600 0x100>;
        clock-frequency = <0>;
        interrupts = <0xA 0x8>;
        interrupt-parent = <&ipic>;
    };
};

里面包含中断值,通过int of_irq_parse_one(struct device_node *device,int index,struct of_phandle_args *out_irq);

解析某一对值,或者我们可以解析原始数据 int of_irq_parse_raw(const __be32 *addr,struct of_phandle_args *out_irq);

addr就指向了某一对值,把里面的中断号中断触发方式解析出来,保存在of_phandle_args结构体中

  • 处理platform_device

of_platform.h   //把device_node转换为platform_device时用到的函数

//根据device_node分配设置platform_device
struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent);
//根据device_node查找到platform_device
of_find_device_by_node
of_device.h     //设备相关的函数,比如of_match_device

of文件分为三类

  1. 处理DTB

  2. 处理device_node

  3. 处理platform_device 设备相关信息

3.3.1.6. 根文件系统中查看设备树

  • 查看原始dtb文件

hexdump -C /sys/firmware/fdt
  • 以目录结构呈现的dtb文件,根节点对应base目录,每一个节点对应一个目录,每一个属性对应一个文件

    比如查看#address-cells的16进制

hexdump -C "#address-cells"

查看compatible

cat compatible

/sys/devices/platform 系统中所有的platform_device,有来自设备树的,也有来自.c文件注册的

对于来自设备树的platform_device。可以进入/sys/devices/platform/<设备名>/of_node 查看它的设备树属性

/proc/device-tree 是链接文件,指向 /sys/firmware/devicetree/base

3.3.2. DTS与DTB

3.3.2.1. device tree结构

dts文件通过编译生成dtb格式文件

../../../_images/Ldd_devicetree_chapter2_1_001.jpg

在描述divice tree结构之前,我们首先明确一个问题device tree是否需要描述系统中的所有硬件信息,答案是否定的。 基本上那些可以动态探测到的设备是不需要描述的,例如usb device,不过对于soc上的usb host controller,它是无法 动态识别的,需要在device tree中描述。同样的道理,在computer system中,PCI device可以被动态探测到不需要在device tree中描述,但是PCI bridge如果不能被探测,那么就需要描述它。

为了了解device tree结构,我们首先给出一个device tree的示例:

/o device-tree
    |-name="device-tree"
    |-model="myboardname"
    |-compatible="myboadfamilyname"
    |-#address-cells=<2>
    |-#size-cells=<2>
    |-linux-phandle=<0>
    |
    o cpus
    ||-name="cpus"
    ||-linux-phandle=<1>
    ||-#address-cless=<1>
    ||-#size-cells=<0>
    |
    o powerpc,970@0
    ||-name="powerpc,970"
    ||-device_type="cpu"
    ||-reg=<0>
    ||-clock-frequency=<0x5f5e1000>
    ||-64-bit
    ||-linux,,phandle=<2>
    |
    o memory@0
    ||-name="meory"
    ||-device_type="memory"
    ||-reg=<0x00000000 0x0000000 0x00000000 0x20000000>
    ||-linux,phandle=<3>
    |
    o chosen
        |-name="chosen"
        |-bootargs="root=/dev/sda2"
        |-linux,phandle=<4>

device tree的基本单元是node,这些node被组织成树状结构,除了root node,每个node都只有一个parent, 一个device tree文件中只能有一个root node。每个node中包含若干的property/value来描述该node的一些 特性。每个node用节点名字(node name)标识,节点名字的格式node-name@unit-address。如果该node没有reg 属性,那么该节点名字中必须不能包括@和unit-address。unit-address的具体格式是和设备挂载在那个bus上 相关。例如对于cpu,其unit-address就是从0开始编址,以此加一。而具体的设备,如以太网控制器,其 unit-address就是寄存器地址。root node的node name是确定的,必须是”/”

在一个树状结构的device_tree中,如果引用一个node呢。要想唯一指定一个node必须使用full path,例如 /node-name-1/node-name-2/node-name-n 在上面的例子中可以通过/cpus/powerpc,970@0访问

属性(property)值标识了设备的特性,他的值(value)是多种多样的 1) 可能为空,也就是没有值的定义,例如上图的64-bit,这个属性没有赋值 2) 可能是一个u32 u64的数值,值的一提的是cell这个术语,在device tree表示32bit的信息单位。例如

::

#address-cells=<1>;

  1. 可能是一个数组,例如<0x00000000 0x00000000 0x00000000 0x20000000>

  2. 可能是一个字符串,例如device_type=”memory”,当然也可能是一个string list.例如powerpc,970

3.3.2.2. device tree source file语法介绍

在linux_kernel中,扩展名为dts的文件就是描述硬件信心的device tree source file,在dts文件中,一个node被定义成

[label:]node-name[@unit-address]{
    [properties definitions]
    [child nodes]
}

“[]”表示option,因此可以定义一个只有node name的空节点。label方便在dts文件中引用。child node的格式和node是 完全一样的。因为,一个dts文件中就是若干个嵌套组成的node,property,以及child node,child node property

如果一个device node中包含了有寻址需求的child node,那么就必须定义这两个属性。(address-cells size-cells). “#”是number的意思。#address-size这个属性用来描述sub node中reg属性地址域特性的。也就是需要多少u32的cell来 描述该地址域,同理可推断#size-cells的含义

chosen node主要用来描述由系统firmware指定的runtime parameter。如果存在chosen node这个节点,那么它的parent

node必须是根节点。command line可以通过bootargs这个property属性传递。initrd的开始地址也可以通过linux,initrd-start 这个property属性传递

"root=/dev/nfs nfsroot=1.1.1.1:/nfsboot ip=1.1.1.2:1.1.1.1:255.255.255.0::usbd0:off console=ttyS0,115200 mem=64M@0x30000000"

通过该command line可以控制内核从usbnet启动,当然,具体项目要相应的修改command line以便应对不同的需求。

device tree用于hw platform识别,runtime parameter传递以及硬件设备描述。chosen节点并没有描述任何硬件设备节点 信息,它只是传递了runtime parameter

aliases节点定义了一些别名,因为device tree是树状结构,当要引用一个node的时候要指明相对于root node的full path。 如果要多次引用就=每次都需要写复杂的字符串,多少有些麻烦。因此可以在aliases节点定义一些设备节点full path的缩写。

memory device node是所有设备树文件的必备机电,它定义了物理内存的layout。device_type属性定义了该node的设备类型 例如cpu、serial。对于memory node其device_type必须等于memory。reg属性定义了访问该device node的地址信息,该属性 值被解析成任意长度的数组(address size),具体用多长的数据来描述address 和size,则根据parent node中的#address-cells和 #size-cells定义。对于device node。reg描述了memory-mapped io register的offset和length。对于memory node则定义了 该memory的起始地址和长度

例如我们的系统是64bit的,physical memory分成两段,定义如下

RAM:start address 0x0,lenth 0x80000000 (2GB)
RAM:start address 0x100000000 length 0x100000000 (4GB)

对于这样的系统,我们可以将root node的#address-cells和#size-cells这两个属性值设定为2,然后用以下方法描述物理内存

#address-cell=<2>;
#size-cells=<2>;

方法一:

memory@0{
    device_type="memory";
    reg=<0x00000000 0x00000000 0x00000000 0x80000000
         0x00000001 0x00000000 0x00000001 0x00000000
    >;
};

方法二:

memory@0{
    device_type="memory";
    reg=<0x00000000 0x00000000 0x00000000 0x80000000>;
};

memory@100000000{
    devuce_type="memory";
    reg=<0x00000001 0x00000000 0x00000001 0x00000000>;
};

以下是s3c24xx.dtsi的一个示例

#include "skeleton.dtsi"

/{
    compatible = "samsung,s3c24xx";--------------A
    interrupt-patent = <&intc>;------------------B

    aliases{
        pinctrl = &pinctrl_0;--------------------C
    };

    intc:interrupt-controller@4a000000{----------D
        compatible = "samsung,s3c2410-irq";
        reg = <0x4a000000 0x100>;
        interrupt-controller;
        #interrupt-cells = <4>;
    };

    serial@50000000{-----------------------------E
        compatible = "samsung,s3c2410-uart";
        reg = <0x50000000 0x4000>;
        interrupts = <1 0 4 28>,<1 1 4 28>;
        status = "disabled";
    };

    pinctrl_0:pinctrl@56000000{------------------F
        compatible = "samsung,s3c2410-wakeup-eint";
        interrrupts = <0 0 0 3>,
                      <0 0 1 3>,
                      <0 0 2 3>,
                      <0 0 3 3>;
    };
};

A) 在描述compatible属性之前要县描述model属性,model属性指定了该设备属于哪一个设备生产商的哪一个model。 一般而言,我们会给model赋值”manufacturer,model”,例如model=”samsung,s3c24xx”.现在回到compatible属性的描述, 该属性的值是一个string list,定义了一系列的modle,这些字符串被kernel哟你过来选择哪一个driver来驱动该设备。 对于root node,compatible属性用来匹配machine type的,对于普通的device node则是用来匹配driver的。

B) 具体各个hw block的interrupt source是如何物理的连接到interrupt controller的呢,在dts文件中是用interrupt- parent这个属性来标识的。如果interrupe-parent的属性值为root node,那么root node会产生中断到interrupt controller么 答案是否定的。

intc是一个lable,标识了一个device node(以上示例中是标识了interrupt-controller@4a000000这个device node)。实际上 interrupt-parent属性值应该是一个u32的整数(这个整数值在device tree的范围内唯一标识了一个的device node,也就是phandle)。 定义了一个lable后,后续可以使用&来引用这个lable,dtc会将lable转换成u32的整数放入dtb中。

在device tree中有一个概念叫interrupt tree,也就是说interrupt也是一个树状结构

../../../_images/interrupt.gif

系统中有一个interrupt tree的根节点,device1、device2以及PCI host brigde的interrupt line都是连接到root interrupt controller的。PCI host bridge设备中有一些下游的设备,也会产生中断,但是他们的中断都是连接到PCI host bridge上的 interrupt controller(术语叫做interrupt nexus),然后报告到root interrupt controller。

  1. pinctrl0也是一个缩写,/pinctrl@56000000的别名

  2. intc是描述interrupt controller的device node。interrupt-controller属性为空,只是用来标识该node是一个interrupt controller 而不是interrupt nexuss

对于根节点必须有个cpus的child node来描述系统中cpu信息。

3.3.2.3. device tree binary格式

经过device tree compiler的编译,device tree source file变成了device tree blob格式

DTB header (struct boot_param_header)

alignment gap

memory reserve map

alignment gap

device-tree structure

alignment gap

device-tree strings

  • dtb header

header fild nam

description

magic

用来识别DTB的,kernel以此确定是dtb还是tag list

totalsize

DTB的它total size

off_dt_struct

device tree structure block的offset

off_dt_strings

device tree strings block的offset

off_mem_rsvmap

offset to memory reserve map

version

该dtb版本

last_comp_ver

兼容版本信息

boot_cpuid_phys

我们在哪个cpu上booting

dt_strings_size

dts block的size,和off_dt_strings确定了strings在内存的位置

dt_struct_size

dts block的size,和off_dt_struct确定了struct在内存中位置

  • memory reserve map的格式描述

这个区域包括若干的reserve memory描述符,每个reserve memory描述符是有address和size组成,其中address和size都是用u64来描述。

  • device tree structure block的格式描述

device tree structure block区域是有若干的分片组成,每个分片开始位置都是保存了token,以此来描述该 分片的属性和内容,共计有5种token

  1. FDT_BEGIN_NODE(0x00000001),该token描述了一个node的开始位置,紧挨着该token的就是node name(包括unit address)

  2. FDT_END_NODE(0x000000002),该token描述了一个node的结束位置

  3. FDT_PROP(0x00000003),该token描述了一个property开始的位置,该token之后是两个u32的数据,分别是length和name offset。 length表示该property value datade size. name offset表示该属性字符串在device tree strings block的偏移。length和name offset 之后就是长度位length的具体属性值数据

  4. FDT_NOP(0x00000004)

  5. FDT_END(0x00000009),该token标识了一个dtb的结束位置

  • 一个dtb的可能结构如下

  1. 若干个FDT_NOP(可选)

  2. FDT_BEGIN_NODE

    node name padding

  3. 若干属性定义

  4. 若干子节点定义(FDT_BEGIN_NODE和FDT_END_NODE包围)

  5. 若干个FDT_NOP

  6. FDT_END_NODE

  7. FDT_END

  • device tree string block的格式描述

device tree strings bloc定义了各个node中使用的属性的字符串表,由于很多属性会出现在多个node中,因此,所有的属性字符串组成了一个 string block。这样可以压缩dtb的size