设备树就是平台总线中的平台设备的衍生,是用于表述硬件设备资源的,对于ARM平台,设备树文件存放在arch/arm/boot/dts下,绑定文档存在Documentation/devicetree/bindings下。

我把jz2440_LED——设备树之点亮LED灯中的设备树文件拷贝过来,进行简单的介绍设备树的格式,

/dts-v1/;

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

    #address-cells = <1>;
    #size-cells = <1>;
        
    memory@30000000 {
        device_type = "memory";
        reg =  <0x30000000 0x4000000>;
    };
/*
    cpus {
        cpu {
            compatible = "arm,arm926ej-s";
        };
    };
*/    
    chosen {
        bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
    };

    
    led {
        compatible = "jz2440_led";  
        pin = <S3C2410_GPF(5)>;
    };
};

/dts-v1/: 第一行的/dts-v1/表示设备树文件的版本没有实际的意义,设备树中的文件每条语句结束时,都会加上";",表示一条语句的完成,和C语言类似。

“/”:表示根节点。

“model”是板的ID,,类型为字符串,它的描述的是板子的型号或者芯片平台的型号,譬如:
model = “SMDK24440”;
model = “Atmel AT91SAM9G20 family SoC”;
从软件的层面讲model属性仅仅表示一个名字而已,没有更多的作用;

“compatible"是平台兼容,一般格式是"manufacturer,model”。内核或者uboot依靠这个属性找到相对应driver,若"compatible"出现多个属性,按序匹配driver;
例如:* compatible = “samsung,smdk2440”, “samsung,s3c24xx”;> //它会优先去内核中寻找samsung,smdk2440,如果没有则寻找samsung,s3c24xx第二项

#address-cells和#size-cells,规范中说明这两个属性是必须的,实际应用时是可选的,这两个属性如果没有都是有默认值的,#address-cells默认值为2,#size-cells默认值为1。
#address-cells和#size-cells属性实际上说明的就是从cpu角度看系统总线的地址长度和大小。
#address-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size)
譬如:
#address-cells = <1>;//表示子节点的地址宽度是32位
#size-cells = <1>;//表示子节点的位宽是32位

"memory"是板级内存的信息。

“device_type":设备类型,寻找节点可以依据这个属性;

“reg"是寄存器,格式是”<address,length>",作为平台内存资源;
譬如:
device_type = “memory”; //表示设备类型是内存
reg = <0x30000000 0x4000000>; //表示寄存器(在嵌入式系统中寄存器和内存是同等对待的)的首地址是0x30000000,大小为0x4000000;

/cpus结点是SOC的CPU信息,可以改变运行频率或者开关CPU它下有1个或多个cpu子结点, cpu子结点中用reg属性用来标明自己是哪一个cpu,
所以 /cpus 中有以下2个属性:
#address-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size) 必须设置为0
譬如:
cpus {
#address-cells = <2>;
#size-cells = <0>;

 cpu@0 {
     device_type = "cpu";
     compatible = "arm,cortex-a53","arm,armv8";
     reg = <0x0 0x0>;
 };
 cpu@1 {
     device_type = "cpu";
     compatible = "arm,cortex-a53","arm,armv8";
     reg = <0x0 0x1>;
 };

};

”chosen“是板级启动参数;
譬如:
chosen {
bootargs = “noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200”;
};

设备树文件包括两种:dts和dtsi。dtsi相当于头文件,其可能包含一些板卡设备的共用部分(被提炼出来,如CPU信息等,多种类板卡共用同一CPU),dts可通过include包含dtsi。
#include “xxx.dtsi”
/include/ “xxx.dtsi”
如果我想在dts中包含dtsi文件

新建 jz2440.dtsi 拷贝jz2440.dts dtsi文件时dts的父节点可以直接引用,语法格式相同, 在dts文件中引用dtsi,比如想修改某个引脚,但是又不想修改dtsi文件,则只需要在dts文件中覆盖掉原来的的配置即可

#include "jz2440.dtsi"
/{
    led {
            ping = <S3C2410_GPF(6)>;
    }
    
}

上传文件, 设置环境变量,编译

如果我想反编译dtb文件怎么做?

当前目录下执行

./scripts/dtc/dtc -I 输入文件dtb -O 输出文件dts -o tmp.dts(输出文件名) 指定dtb文件所在位置
./scripts/dtc/dtc -I dtb -O dts -o tmp.dts arch/arm/boot/dts/jz2440.dtb

dts设备树文件中aliases用法_寄存器

发现修改后寄存器值变了 再次修改 在dtsi中的led节点上添加lable

LED:led {
    compatible = "jz2440_led";
    pin = <S3C2410_GPF(5)>;
};

在dts文件中覆盖(可以给一个设备节点添加label,之后可以通过&label的形式访问这个label)

&LED{
    pin = <S3C2410_GPF(7)>;
};

上传文件, 设置环境变量,编译,反编译dtb查看已经变化

设备树的文件结构和剖析

dts设备树文件中aliases用法_子节点_02

设备树的应用

在具体的工程里如何做设备树呢?这里介绍三大法宝:文档、脚本、代码

1. 文档是对各种 node 的描述,位于内核 documentation/devicetree/bingdings/arm/ 下

2. 脚本就是设备树 dts,代码就是你要写的设备代码,一般位于 arch/arm/ 下,以后在写设备的时候可以用这种方法,绝对的事半功倍

设备文件就是根据各种内核态的 API 来调用设备树里的板级信息:

truct device_node *of_find_node_by_phandle(phandle handle);

struct device_node *of_get_parent(const struct device_node_ *node);

of_get_child_count()

of_property_read_u32_array()

of_property_read_u64()

of_property_read_string()

of_property_read_string_array()

of_property_read_bool()

设备树可以总结为三大作用

一是平台标识,所谓平台标识就是板级识别,让内核知道当前使用的是哪个开发板,这里识别的方式是根据 root 节点下的 compatible 字段来匹配。

二是运行时配置,就是在内核启动的时候 ramdisk 的配置,比如 bootargs 的配置,ramdisk 的起始和结束地址。

三是设备信息集合,这也是最重要的信息,集合了各种设备控制器

dts设备树文件中aliases用法_子节点_03