1 dts 文件格式
1.1 DTS文件布局(layout):
cpp
/dts-v1/;
[memory reservations]
/ {
[property definitions]
[child nodes]
};
(1)第一行表示当前设备树文件的版本
(2)第二行用来定义保留的内存区域,该区域不给内核使用,例如:
cpp
/memresersve/ 0x33000000 0x10000
(3)第三行的 "/" 定义根节点
(4)根节点中包含根节点属性(property definitions)和子节点(child nodes)
[注意]: 大括号和属性都要在结尾处加 ";"
1.2 节点属性格式
(1)Property格式2(没有值):
cpp
[label:] property-name;
(2)Property格式1:
cpp
[label:] property-name = value;
(3)Property取值有3种情况:
① arrays of cells(1个或多个32位数据, 64位数据使用2个32位数据表示)
示例1:Arrays of cells : cell就是一个32位的数据
cpp
interrupts = <17 0xc>;
示例2:64bit数据使用2个cell来表示:
cpp
clock-frequency = <0x00000001 0x00000000>;
② string(字符串)
示例:A null-terminated string (有结束符的字符串):
cpp
compatible = "simple-bus";
③ bytestring(1个或多个字节)
示例:A bytestring(字节序列) :
cpp
local-mac-address = [00 00 12 34 56 78]; // 每个byte使用2个16进制数来表示
local-mac-address = [000012345678]; // 每个byte使用2个16进制数来表示(空格可以省略)
[补充]: Property的取值还可以是各种值的组合(该用法不常见), 用逗号隔开:
cpp
compatible = "ns16550", "ns8250";
example = <0xf00f0000 19>, "a strange property format";
1.3 设备节点的格式
Devicetree node格式:
cpp
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
};
1.4 特殊的、默认的属性
(1)根结点的属性
cpp
#address-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size)
compatible // 定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备
// 即这个板子兼容哪些平台
// uImage : smdk2410 smdk2440 mini2440 ==> machine_desc
model // 咱这个板子是什么
// 比如有2款板子配置基本一致, 它们的compatible是一样的
// 那么就通过model来分辨这2款板子
[补充]: 关于 #address-cells 和 #size-cells
cpp
/ {
model = "SMDK24440";
compatible = "samsung,smdk2440";
#address-cells = <1>;
#size-cells = <1>;
memory@30000000 {
device_type = "memory";
reg = <0x30000000 0x4000000>;
};
}
由于#address-cells和#size-cells的值都为1,所以上述例子reg的值中0x30000000表示地址,而0x4000000就表示地址的长度。已知#address-cells和#size-cells,reg可以区分多组地址段:
cpp
/ {
model = "SMDK24440";
compatible = "samsung,smdk2440";
#address-cells = <1>;
#size-cells = <1>;
memory@30000000 {
device_type = "memory";
reg = <0x30000000 0x4000000 0 4096>;
};
}
上述例子中的reg表示两组数据,第一组是地址为0x30000000,长度为0x4000000的地址段;而第二组数据是地址为0,长度为4096的地址段。
(2)/memory 结点
cpp
device_type = "memory";
reg // 用来指定内存的地址、大小
(3)/chosen 结点
cpp
bootargs // 内核command line参数, 跟u-boot中设置的bootargs作用一样
(4)/cpus 结点
/cpus节点下有1个或多个cpu子节点, cpu子节点中用reg属性用来标明自己是哪一个cpu
所以 /cpus 中有以下2个属性:
cpp
#address-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size)
// 必须设置为0
(5)/cpus/cpu* 结点(多个cpu中的每个cpu结点)
cpp
device_type = "cpu";
reg // 表明自己是哪一个cpu
1.5 引用其他节点
(1)phandle : // 节点中的phandle属性, 它的取值必须是唯一的(不要跟其他的phandle值一样)
cpp
pic@10000000 {
phandle = <1>;
interrupt-controller;
};
another-device-node {
interrupt-parent = <1>; // 使用phandle值为1来引用上述节点
};
(2)label:
cpp
PIC: pic@10000000 {
interrupt-controller;
};
another-device-node {
interrupt-parent = <&PIC>; // 使用label来引用上述节点,
// 使用lable时实际上也是使用phandle来引用,
// 在编译dts文件为dtb文件时, 编译器dtc会在dtb中插入phandle属性
};
1.6 dtsi 文件
dtsi文件的格式和dts文件的格式一样,可以将公共的部分放到dtsi文件,并让dts文件包含该dtsi文件。在dts中包含dtsi文件的方法:
cpp
#include "jz2440.dtsi"
下面举两个dtsi文件相关的例子:
(1)示例1:覆盖结点
① 文件:jz2440.dtsi
cpp
/dtsi-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";
reg = <5>;
};
};
② 文件:mytree.dts
cpp
/dts-v1/;
#include "jz2440.dtsi"
/ {
led {
reg = <6>;
};
};
[说明]: 在mytree.dts中包含jz2440.dtsi文件,然后按照jz2440.dtsi中led的结点路径,重写led结点,并修改其reg属性为6。这样在最终编译生成的dtb文件中,led的reg属性就为6。也就是说在dts文件中可以覆盖dtsi文件的结点和属性。
(2)示例2:标签引用
① 文件:jz2440.dtsi
cpp
/dtsi-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: led {
compatible = "jz2440_led";
reg = <5>;
};
};
② 文件:mytree.dts
cpp
/dts-v1/;
#include "jz2440.dtsi"
&LED {
reg = <5>;
};
[说明]: 依旧让mytree.dts中包含jz2440.dtsi文件。但是这里通过led结点的标签LED来引用该结点,注意通过标签引用结点时不要从根路径开始写结点,否则会报错。
(3)dtc工具
- dtc其实就是device-tree-compiler,也就是设备树文件dts的编译器。
- dtc可以将dts和dtsi文件编译成dtb二进制文件,也可以将dtb文件反编译成dts文件。
- dtc工具在Linux内核的scripts/dtc目录下,因此需要编译完内核源码后才会生成。
2 dtb 文件格式
2.1 dtb文件的布局
cpp
------------------------------
base -> | struct boot_param_header |
------------------------------
| (alignment gap) (*) |
------------------------------
| memory reserve map |
------------------------------
| (alignment gap) |
------------------------------
| |
| device-tree structure |
| |
------------------------------
| (alignment gap) |
------------------------------
| |
| device-tree strings |
| |
-----> ------------------------------
|
|
--- (base + totalsize)
2.2 dtb二进制文件的分析
2.3 dtb文件采用大端存储模式
(1)大端存储模式和小端存储模式
(2)补充
大端模式和小端模式是针对数值存储来说的,字符串存储则没有该规则。例如字符串"ab","b"总是存储在比"a"高的地址中。
2.4 查看二进制文件的工具
- UltraEdit
- Free-Hex-Editor-Neo
3 参考
(1)官方文档: Specifications - DeviceTree
(2)内核文档1: Documentation/devicetree/usage-model.txt
(3)内核文档2: Documentation/devicetree/booting-without-of.txt