参考:https://www.devicetree.org/specifications/
一、什么是设备树
1、设备树的引入
Linus Torvalds在2011年3月17日的ARM Linux邮件列表宣称“this whole ARM thing is a fucking pain in the ass”,ARM Linux社区对此作出了回应,引入设备树。
这是因为linux内核随着不断的发展维护,充斥着大量的板级文件细节,各种芯片厂商的arch/arm/plat-xxx和arch/arm/mach-xxx使得内核越来越臃肿庞大。这种以树状节点的方式描述一个设备的各种硬件信息:CPU、GPIO、时钟、中断、内存等,形成类似文本文件,很好的解决了这些问题。
2、设备树特点
a、对于传统字符驱动的编写有两种方式:
一是在驱动程序中,直接写死硬件资源,如:GPIO、寄存器地址、中断号等,使得硬件改动时,必须修改驱动程序。
二是采用总线驱动platform模型,将硬件资源与驱动软件分离,在platform_device中描述硬件资源,arch/arm/mach-xxx对应的文件,便是以platform_device描述各自CPU对应的硬件资源;在platform_driver中分配/设置/注册 file_operations结构体, 并从platform_device获得硬件资源。这种编写方式使得驱动易于扩展,硬件改动时只需修改platform_device或者platform_driver,这就导致linux内核产生大量的冗余代码。
b、 使用设备树的特点在于,在设备树dts文件指定硬件资源,dts被编译为dtb文件, 在启动单板时,U-boot会将dtb文件传给内核,使得驱动程序与硬件分离,我们只需要修改dts文件,便能实现需求。这就是设备树易于扩展,硬件有变动时不需要重新编译内核或驱动程序,只需要提供不一样的dtb文件。
3、编译设备树
设备树文件的格式为dts,包含的头文件格式为dtsi,dts文件是一种人可以看懂的编码格式。但是uboot和linux不能直接识别,他们只能识别二进制文件,所以需要把dts文件编译成dtb文件。dtb文件是一种可以被kernel和uboot识别的二进制文件。把dts编译成dtb文件的工具是dtc。Linux源码目录下scripts/dtc目录包含dtc工具的源码。在Linux的scripts/dtc目录下除了提供dtc工具外,也可以自己安装dtc工具,linux下执行:sudo apt-get install device-tree-compiler安装dtc工具。其中还提供了一个fdtdump的工具,可以反编译dtb文件。dts和dtb文件的转换如图所示。
dtc工具的使用方法是:dtc –I dts –O dtb –o xxx.dtb xxx.dts,即可生成dts文件对应的dtb文件了。
在编译linux内核时。也可以直接make dtbs生成dtb文件。
二、设备树dts格式
1、dts格式
a、dts文件的基本组成单元为:
/dts-v1/; //表示dts文件的版本号
[memory reservations] //表示内存保留,即CPU是否需要在flash上保留一段内存用作他用,可以有也可以没有
/ { //表示根节点
[property definitions] //就是属性定义,对当前节点描述,将硬件信息提供给内核处理
[child nodes] //子节点
};
b、从s3c6410-mini6410.dts截取一段dts文件加以分析,dts文件也像C一样,可以包含.dtsi文件和.h文件,引用变量,引用节点
/dts-v1/;
#include
#include
#include "s3c6410.dtsi"
/ {
model = "FriendlyARM Mini6410 board based on S3C6410";
compatible = "friendlyarm,mini6410", "samsung,s3c6410";
memory@50000000 {
device_type = "memory";
reg = <0x50000000 0x10000000>;
};
}
[property definitions]:
model = "FriendlyARM Mini6410 board based on S3C6410"; //对设备制造商的描述
compatible = "friendlyarm,mini6410", "samsung,s3c6410"; //对单板的描述,即整个dts文件支持描述的两个开发板
这两个属性的定义便是对根节点的描述,也就是对s3c6410-mini6410.dts文件的描述。
[child nodes]:
memory@50000000 {
device_type = "memory";
reg = <0x50000000 0x10000000>;
};
memory@50000000是根节点 / 的子节点,同样通过[property definitions]即device_type 属性和reg属性描述节点。
c、在dts文件中,对于properties,有一些常用的、默认的、特殊的属性,定义如下:
model
设备制造商的描述,如果有2款板子配置基本一致, 它们的compatible是一样的那么就通过model来分辨这2款板子
compatible
定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备,即这个板子兼容哪些平台
reg
描述设备资源在其父总线定义的地址空间中的地址。通常这意味着内存映射IO寄存器块的偏移量和长度,但在某些总线类型上可能有不同的含义。根节点定义的地址空间中的地址是CPU实际地址。
#address-cells
在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells
在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size)
phandle
节点中的phandle属性, 它的取值必须是唯一的(不要跟其他的phandle值一样),使用phandle值来引用节点
bootargs
内核command line参数, 跟u-boot中设置的bootargs作用一样
d、在dts文件中,引用其他节点:
1)phandle方式引用
pic@10000000 {
phandle = <1>;
interrupt-controller;
};
another-device-node {
interrupt-parent = <1>; // 使用phandle值为1来引用上述节点
};
2)label 方式引用
PIC: pic@10000000 {
interrupt-controller;
};
another-device-node {
interrupt-parent = <&PIC>; // 使用label来引用上述节点,
// 使用lable时实际上也是使用phandle来引用,
// 在编译dts文件为dtb文件时, 编译器dtc会在dtb中插入phandle属性
};
3)直接引用
vic0: interrupt-controller@71200000 {
compatible = "arm,pl192-vic";
interrupt-controller;
reg = <0x71200000 0x1000>;
#interrupt-cells = <1>;
};
&vic0 {//直接引用vic0 节点,添加属性
valid-mask = <0xffffff7f>;
valid-wakeup-mask = <0x00200004>;
};
e、对于节点的属性描述,具有几种不同类型,如图所示:
举例说明:
1). Arrays of cells : cell就是一个32位的数据
interrupts = <17 0xc>;
2). 64bit数据使用2个cell来表示:
clock-frequency = <0x00000001 0x00000000>;
3). A null-terminated string (有结束符的字符串):
compatible = "simple-bus";
4). A bytestring(字节序列) :
local-mac-address = [00 00 12 34 56 78]; // 每个byte使用2个16进制数来表示
local-mac-address = [000012345678]; // 每个byte使用2个16进制数来表示
5). 可以是各种值的组合, 用逗号隔开:
compatible = "ns16550", "ns8250";
example = <0xf00f0000 19>, "a strange property format";
davicom,no-eeprom;
三、设备树dtb格式
dtb文件是dts编译后的供内核使用的文件,分析dtb文件,有助于后面深入理解设备树文件在内核中是如何被解析的,如何在设备驱动中使用。
1、dtb文件结构
从结构图中可以看出,dtb文件主要分为四块:dtb头部信息块、内存保留信息块、dts文件属性的值信息块、dts文件属性名信息块。
a、dtb头部信息块在dtb文件中的描述,完整的描述了dtb文件信息,内核通过这些头部信息即可解析设备树文件
struct fdt_header {
uint32_t magic; //0xd00dfeed
uint32_t totalsize; //dtb文件的大小
uint32_t off_dt_struct; //dts文件属性的值信息块的偏移地址
uint32_t off_dt_strings; //dts文件属性名信息块的偏移地址
uint32_t off_mem_rsvmap; //dts文件内存保留信息块的偏移地址
uint32_t version;
uint32_t last_comp_version;
uint32_t boot_cpuid_phys;
uint32_t size_dt_strings;
uint32_t size_dt_struct;
};
b、内存保留信息块在dtb文件中的描述
struct fdt_reserve_entry {
uint64_t address; //保留内存的起始地址
uint64_t size; //保留地址的大小
};
c、dts文件属性的值信息块在dtb文件中的描述
struct {
uint32_t len; //属性值得长度
uint32_t nameoff; //属性名字在dtb文件中的偏移地址:off_dt_struct+nameoff 即为属性名字的地址
}
节点的一个属性描述以0x00000003开始,32byte表示属性值得长度,32byte表示属性名字在dtb文件中的的偏移地址,紧随其后填充属性值value。
d、dts文件属性名信息块在dtb文件中的描述
dts文件在编译时将dts文件中的属性名从off_dt_strings依次写入dtb文件,内核解析时通过off_dt_strings和dts文件属性的值信息块即可准确解析属性名。
2、dtb文件对dts文件中节点的描述
如图所示,通过dtb文件属性的值信息块和dts文件属性名信息块,在dtb文件中根节点以0x00000001开始,以0x00000009结束,子节点以0x00000001开始,以0x00000002结束,每个属性描述以0x00000003开始,描述节点信息。
3、举例说明
随便打开一个编译好的dtb文件,分析如图所示
附上分析的dts和dtb文件
// SPDX-License-Identifier: GPL-2.0
/*
* SAMSUNG SMDK2440 board device tree source
*
* Copyright (c) 2018 [email protected]
* dtc -I dtb -O dts -o jz2440.dts jz2440.dtb
*/
#define S3C2410_GPA(_nr) ((0<<16) + (_nr))
#define S3C2410_GPB(_nr) ((1<<16) + (_nr))
#define S3C2410_GPC(_nr) ((2<<16) + (_nr))
#define S3C2410_GPD(_nr) ((3<<16) + (_nr))
#define S3C2410_GPE(_nr) ((4<<16) + (_nr))
#define S3C2410_GPF(_nr) ((5<<16) + (_nr))
#define S3C2410_GPG(_nr) ((6<<16) + (_nr))
#define S3C2410_GPH(_nr) ((7<<16) + (_nr))
#define S3C2410_GPJ(_nr) ((8<<16) + (_nr))
#define S3C2410_GPK(_nr) ((9<<16) + (_nr))
#define S3C2410_GPL(_nr) ((10<<16) + (_nr))
#define S3C2410_GPM(_nr) ((11<<16) + (_nr))
/dts-v1/;
/memreserve/ 0x33f00000 0x100000;
/ {
model = "SMDK24440";
compatible = "samsung,smdk2440";
#address-cells = <1>;
#size-cells = <1>;
memory {
device_type = "memory";
reg = <0x30000000 0x4000000 0 4096>;
};
chosen {
bootargs = "noinitrd root=/dev/mtdblock4 \
rw init=/linuxrc console=ttySAC0,115200";
};
led {
compatible = "jz2440_led";
pin = ;
};
};
00000000 d0 0d fe ed 00 00 01 d1 00 00 00 48 00 00 01 88 |...........H....|
00000010 00 00 00 28 00 00 00 11 00 00 00 10 00 00 00 00 |...(............|
00000020 00 00 00 49 00 00 01 40 00 00 00 00 33 f0 00 00 |[email protected]...|
00000030 00 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 |................|
00000040 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 |................|
00000050 00 00 00 03 00 00 00 0a 00 00 00 00 53 4d 44 4b |............SMDK|
00000060 32 34 34 34 30 00 00 00 00 00 00 03 00 00 00 11 |24440...........|
00000070 00 00 00 06 73 61 6d 73 75 6e 67 2c 73 6d 64 6b |....samsung,smdk|
00000080 32 34 34 30 00 00 00 00 00 00 00 03 00 00 00 04 |2440............|
00000090 00 00 00 11 00 00 00 01 00 00 00 03 00 00 00 04 |................|
000000a0 00 00 00 20 00 00 00 01 00 00 00 01 6d 65 6d 6f |... ........memo|
000000b0 72 79 00 00 00 00 00 03 00 00 00 07 00 00 00 2c |ry.............,|
000000c0 6d 65 6d 6f 72 79 00 00 00 00 00 03 00 00 00 10 |memory..........|
000000d0 00 00 00 38 30 00 00 00 04 00 00 00 00 00 00 00 |...80...........|
000000e0 00 00 10 00 00 00 00 02 00 00 00 01 63 68 6f 73 |............chos|
000000f0 65 6e 00 00 00 00 00 03 00 00 00 47 00 00 00 3c |en.........G...<|
00000100 6e 6f 69 6e 69 74 72 64 20 72 6f 6f 74 3d 2f 64 |noinitrd root=/d|
00000110 65 76 2f 6d 74 64 62 6c 6f 63 6b 34 20 09 09 72 |ev/mtdblock4 ..r|
00000120 77 20 69 6e 69 74 3d 2f 6c 69 6e 75 78 72 63 20 |w init=/linuxrc |
00000130 63 6f 6e 73 6f 6c 65 3d 74 74 79 53 41 43 30 2c |console=ttySAC0,|
00000140 31 31 35 32 30 30 00 00 00 00 00 02 00 00 00 01 |115200..........|
00000150 6c 65 64 00 00 00 00 03 00 00 00 0b 00 00 00 06 |led.............|
00000160 6a 7a 32 34 34 30 5f 6c 65 64 00 00 00 00 00 03 |jz2440_led......|
00000170 00 00 00 04 00 00 00 45 00 05 00 05 00 00 00 02 |.......E........|
00000180 00 00 00 02 00 00 00 09 6d 6f 64 65 6c 00 63 6f |........model.co|
00000190 6d 70 61 74 69 62 6c 65 00 23 61 64 64 72 65 73 |mpatible.#addres|
000001a0 73 2d 63 65 6c 6c 73 00 23 73 69 7a 65 2d 63 65 |s-cells.#size-ce|
000001b0 6c 6c 73 00 64 65 76 69 63 65 5f 74 79 70 65 00 |lls.device_type.|
000001c0 72 65 67 00 62 6f 6f 74 61 72 67 73 00 70 69 6e |reg.bootargs.pin|
000001d0 00 |.|
000001d1