设备树就是平台总线中的平台设备的衍生,是用于表述硬件设备资源的,对于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
发现修改后寄存器值变了 再次修改 在dtsi中的led节点上添加lable
LED:led {
compatible = "jz2440_led";
pin = <S3C2410_GPF(5)>;
};
在dts文件中覆盖(可以给一个设备节点添加label,之后可以通过&label的形式访问这个label)
&LED{
pin = <S3C2410_GPF(7)>;
};
上传文件, 设置环境变量,编译,反编译dtb查看已经变化
设备树的文件结构和剖析
设备树的应用
在具体的工程里如何做设备树呢?这里介绍三大法宝:文档、脚本、代码
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 的起始和结束地址。
三是设备信息集合,这也是最重要的信息,集合了各种设备控制器