系统启动以后可以在根文件系统里面看到设备树的节点信息。在/proc/device-tree/目录下存放着设备树信息。
内核启动的时候会解析设备树,然后在/proc/device-tree/目录下呈现出来
属性名陌生只能说明定义该属性的开发人员与你命名属性的风格不同
参考https://baijiahao.baidu.com/s?id=1675457952755522372&wfr=spider&for=pcx
https://blog.csdn.net/qq_35031421/article/details/105002645
设备树这个概念并不是一开始就具有的,它的出现是Linus Torvalds在2011年3月,对于kernel/arch/arm/plat-xxx和kernel/arch/arm/mach-xxx含有大量的描述板级细节的代码,针对这种现象提出了设备树(Device tree)的概念。设备树其实就是描述硬件的数据结构,可以将很多硬件信息直接传递给Linux,省去了内核大量的冗余代码。
设备树含有的节点和属性描述的信息主要有时钟、中断、GPIO控制器,外设连接情况,总线,内存基地址等等。Linux设备树主要由DTS、DTC、DTB等几个文件格式组成。
DTS文件是一个以ASCII文本格式为主要描述语言的设备树。一个.dts对应一个ARM设备,类似于C语言的.C文件。
由于设备较多此时共用的部分提取成为.dtsi,类似于C语言的头文件,他们可以相互包含。
DTC说白了就是将.dts编译为.dtb的工具。类似于C语言的编译器VC++。DTB是经过DTC编译后的二进制格式的设备树描述,类似于C语言编译生成的HEX文件。
总而言之,设备树的出现大大减少了Linux的代码,去其糟粕,取其精华,让内核相关代码更加简洁易懂。这样更像人类的发展历程波浪式前进,螺旋式上升。
设备树是一种描述硬件的数据结构,它起源于OpenFirmware(OF)。在Linux 2.6中, ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx中,采用设备树后,许多硬件的细节可以直接通过它传递给Linux,而不再需要在内核中进行大量的冗余编码。
compatible 兼容的model 模特儿,模型,型号handle: n. 把手;柄Status 状态身份
cell 细胞,电池,单元格,手机range 值域范围device 设备,装置aliases 别名label 标签,标记,标号
.dts文件是设备树的源文件。
SoC称为系统级芯片。由于一个SoC可能对应多个设备,这些.dst文件可能包含很多共同的部分,共同的部分一般被提炼为一个 .dtsi 文件,这个文件相当于C语言的头文件。
DTC是将.dts编译为.dtb的工具,相当于gcc。
.dtb文件是 .dts 被 DTC 编译后的二进制格式的设备树文件,它可以被linux内核解析。
《Power_ePAPR_APPROVED_v1.12》
和 C 语言一样,设备树也支持头文件,设备树的头文件扩展名为 .dtsi;同时也可以像C 语言一样包含 .h头文件;
例如:(代码来源linux-4.15/arch/arm/boot/dts/s3c2416.dtsi)
#include
#include "s3c24xx.dtsi"
注:.dtsi 文件一般用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如 UART、 IIC 等等。
SoC称为系统级芯片,也有称片上系统,意指它是一个产品,是一个有专用目标的集成电路,其中包含完整系统并有嵌入软件的全部内容。
node-name@unit-address
node-name: 是设备节点的名称,为ASCII字符串,节点名字应该能够清晰的描述出节点的功能,比如“uart1”就表示这个节点是UART1外设;
unit-address: 一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话 “unit-address” 可以不要;
注:根节点没有node-name 或者 unit-address,它被定义为 /。
设备节点的例子如下图:
在上图中:cpu 和 ethernet依靠不同的unit-address 分辨不同的CPU;可见,node-name相同的情况下,可以通过不同的unit-address定义不同的设备节点。
uart n,串口 memory n.内存,记忆 ether以太 ethernet以太网
以太是古希腊哲学家亚里士多德所设想的一种物质
PIC: pic@10000000 {
interrupt-controller;
};
another-device-node {
interrupt-parent = <&PIC>; // 使用label来引用上述节点,
// 使用lable时实际上也是使用phandle来引用,
// 在编译dts文件为dtb文件时, 编译器dtc会在dtb中插入phandle属性
}
自定义属性看绑定文件/home/lj/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_zhengdianyuanzi/Documentation/devicetree/bindings
compatible 属性也叫做 “兼容性” 属性,这是非常重要的一个属性! compatible 属性的值是一个字符串列表, compatible 属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序。compatible 属性值的推荐格式:
"manufacturer,model"
① manufacturer : 表示厂商;② model : 一般是模块对应的驱动名字。manufacturer n.生产商,制造厂
例如:
compatible = "fsl,mpc8641", "ns16550";
上面的compatible有两个属性,分别是 “fsl,mpc8641” 和 “ns16550”;其中 “fsl,mpc8641” 的厂商是 fsl;设备首先会使用第一个属性值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件;如果没找到,就使用第二个属性值查找,以此类推,直到查到到对应的驱动程序 或者 查找完整个 Linux 内核也没有对应的驱动程序为止。
注:一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。
model 属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的,例如:
model = "Samsung S3C2416 SoC";
phandle属性为devicetree中唯一的节点指定一个数字标识符,节点中的phandle属性,它的取值必须是唯一的(不要跟其他的phandle值一样),例如:
pic@10000000 {
phandle = <1>;
interrupt-controller;
};
another-device-node {
interrupt-parent = <1>; // 使用phandle值为1来引用上述节点
};
注:DTS中的大多数设备树将不包含显式的phandle属性,当DTS被编译成二进制DTB格式时,DTC工具会自动插入phandle属性。
status 属性看名字就知道是和设备状态有关的, status 属性值也是字符串,字符串是设备的状态信息,可选的状态如下表所示:
#address-cells 和 #size-cells的值都是无符号 32 位整型,可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。
#address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32 位),
#size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位)。
#address-cells 和 #size-cells 表明了子节点应该如何编写 reg 属性值,一般 reg 属性都是和地址有关的内容,和地址相关的信息有两种:
起始地址和地址长度,reg 属性的格式为:
reg = <address1 length1 address2 length2 address3 length3……>
例如一个64位的处理器:
soc {
#address-cells = <2>;
#size-cells = <1>;
serial {
compatible = "xxx";
reg = <0x4600 0x5000 0x100>; /*地址信息是:0x00004600 00005000,长度信息是:0x100*/
};
};
reg 属性的值一般是 (address, length) 对,reg 属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。例如:一个设备有两个寄存器块,一个的地址是0x3000,占据32字节;另一个的地址是0xFE00,占据256字节,表示如下:
reg = <0x3000 0x20 0xFE00 0x100>;
注:上述对应#address-cells = <1>; #size-cells = <1>;。
也说明该设备统一编址了,对应汇编指令MOV。out和in对应没有统一编址
ranges属性值可以为空或者按照 (child-bus-address,parent-bus-address,length) 格式编写的数字矩阵, ranges 是一个地址映射/转换表, ranges 属性每个项目由子地址、父地址和地址空间长度这三部分组成:
child-bus-address: 子总线地址空间的物理地址,由父节点的 #address-cells 确定此物理地址所占用的字长。
parent-bus-address: 父总线地址空间的物理地址,同样由父节点的 #address-cells 确定此物理地址所占用的字长。
length: 子地址空间的长度,由父节点的 #size-cells 确定此地址长度所占用的字长。
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x0 0xe0000000 0x00100000>;
serial {
device_type = "serial";
compatible = "ns16550";
reg = <0x4600 0x100>;
clock-frequency = <0>;
interrupts = <0xA 0x8>;
interrupt-parent = <&ipic>;
};
};
节点 soc 定义的 ranges 属性,值为 <0x0 0xe0000000 0x00100000>,此属性值指定了一个 1024KB(0x00100000) 的地址范围,子地址空间的物理起始地址为 0x0,父地址空间的物理起始地址为 0xe0000000。
serial 是串口设备节点, reg 属性定义了 serial 设备寄存器的起始地址为 0x4600,寄存器长度为 0x100。经过地址转换, serial 设备可以从 0xe0004600 开始进行读写操作,0xe0004600=0x4600+0xe0000000。
name 属性值为字符串, name 属性用于记录节点名字, name 属性已经被弃用,不推荐使用name 属性,一些老的设备树文件可能会使用此属性。
device_type 属性值为字符串, IEEE 1275 会用到此属性,用于描述设备的 FCode,但是设备树没有 FCode,所以此属性也被抛弃了。此属性只能用于 cpu 节点或者 memory 节点。
memory@30000000 {
device_type = "memory";
reg = <0x30000000 0x4000000>;
};
每个设备树文件只有一个根节点,其他所有的设备节点都是它的子节点,它的路径是 /。根节点有以下属性:
例如:compatible = “samsung,smdk2440”,“samsung,s3c24xx” ,内核会优先寻找支持smdk2440的machine_desc结构体,如果找不到才会继续寻找支持s3c24xx的machine_desc结构体(优先选择第一项,然后才是第二项,第三项…)
内核提供了一个重要的结构体struct machine_desc ,这个结构体在内核移植中起到相当重要的作用,内核通过machine_desc结构体来控制系统体系架构相关部分的初始化。
machine n. 机械装置
/* 在文件:arch/arm/include/asm/mach/arch.h */
struct machine_desc {
unsigned int nr; /* architecture number */
const char *name; /* architecture name */
unsigned long boot_params; /* tagged list */
const char **dt_compat; /* array of device tree
* 'compatible' strings */
unsigned int nr_irqs; /* number of IRQs */
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned int reserve_lp0 :1; /* never has lp0 */
unsigned int reserve_lp1 :1; /* never has lp1 */
unsigned int reserve_lp2 :1; /* never has lp2 */
unsigned int soft_reboot :1; /* soft reboot */
void (*fixup)(struct machine_desc *,
struct tag *, char **,
struct meminfo *);
void (*reserve)(void);/* reserve mem blocks */
void (*map_io)(void);/* IO mapping function */
void (*init_early)(void);
void (*init_irq)(void);
struct sys_timer *timer; /* system tick timer */
void (*init_machine)(void);
#ifdef CONFIG_MULTI_IRQ_HANDLER
void (*handle_irq)(struct pt_regs *);
#endif
};
aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。例如:定义 flexcan1 和 flexcan2 的别名是 can0 和 can1。
aliases {
can0 = &flexcan1;
can1 = &flexcan2;
};
所有设备树都需要一个memory设备节点,它描述了系统的物理内存布局。如果系统有多个内存块,可以创建多个memory节点,或者可以在单个memory节点的reg属性中指定这些地址范围和内存空间大小。
例如:一个64位的系统有两块内存空间:RAM1: 起始地址是0x0,地址空间是 0x80000000;RAM2: 起始地址是0x10000000,地址空间也是0x80000000;同时根节点下的 #address-cells = <2>和#size-cells = <2>,这个memory节点描述为:
memory@0 {
device_type = "memory";
reg = <0x00000000 0x00000000 0x00000000 0x80000000
0x00000000 0x10000000 0x00000000 0x80000000>;
或者:
memory@0 {
device_type = "memory";
reg = <0x00000000 0x00000000 0x00000000 0x80000000>;
};
memory@10000000 {
device_type = "memory";
reg = <0x00000000 0x10000000 0x00000000 0x80000000>;
};
chosen 并不是一个真实的设备, chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。例如:
```c
chosen {
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};
cpus节点下有1个或多个cpu子节点, cpu子节点中用reg属性用来标明自己是哪一个cpu,所以 /cpus 中有以下2个属性:
#address-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size)
// 必须设置为
例如:
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
device_type = "cpu";
reg = <0>;
cache-unified;
cache-size = <0x8000>; // L1, 32KB
cache-block-size = <32>;
timebase-frequency = <82500000>; // 82.5 MHz
next-level-cache = <&L2_0>; // phandle to L2
L2_0:l2-cache {
compatible = "cache";
cache-unified;
cache-size = <0x40000>; // 256 KB
cache-sets = <1024>;
cache-block-size = <32>;
cache-level = <2>;
next-level-cache = <&L3>; // phandle to L3
L3:l3-cache {
compatible = "cache";
cache-unified;
cache-size = <0x40000>; // 256 KB
cache-sets = <0x400>; // 1024
cache-block-size = <32>;
cache-level = <3>;
};
};
};
cpu@1 {
device_type = "cpu";
reg = <1>;
cache-unified;
cache-block-size = <32>;
cache-size = <0x8000>; // L1, 32KB
timebase-frequency = <82500000>; // 82.5 MHzclock-frequency = <825000000>; // 825 MHz
cache-level = <2>;
next-level-cache = <&L2_1>; // phandle to L2
L2_1:l2-cache {
compatible = "cache";
cache-unified;
cache-size = <0x40000>; // 256 KB
cache-sets = <0x400>; // 1024
cache-line-size = <32>; // 32 bytes
next-level-cache = <&L3>; // phandle to L3
};
};
};
节点中的phandle属性, 它的取值必须是唯一的(不要跟其他的phandle值一样)
pic@10000000 {
phandle = <1>;
interrupt-controller;
};
another-device-node {
interrupt-parent = <1>; // 使用phandle值为1来引用上述节点
};
PIC: pic@10000000 {
interrupt-controller;
};
another-device-node {
interrupt-parent = <&PIC>; // 使用label来引用上述节点,
// 使用lable时实际上也是使用phandle来引用,
// 在编译dts文件为dtb文件时, 编译器dtc会在dtb中插入phandle属性
};
reservation 保留,预订,预定block 块,街区,代码块Structure 结构,构造,建筑物String 字符串,绳子,字串
.dtb文件是 .dts 被 DTC 编译后的二进制格式的设备树文件,它的文件布局如下:
(1) DTB文件可以分为四个部分:struct ftd_header、memory reservation block、structure block、strings block;
(2) 最开始的为struct ftd_header,包含其它三个部分的偏移地址;
(3) memory reservation block记录保留内存信息;
(4) structure block保存节点的信息,节点的结构;
(5) strings block保存属性的名字,将属性名字单独作为字符串保存;
(1)
struct ftd_header结构体的定义如下:
struct fdt_header {
uint32_t magic; /*它的值为0xd00dfeed,以大端模式保存*/
uint32_t totalsize; /*整个DTB文件的大小*/
uint32_t off_dt_struct; /*structure block的偏移地址*/
uint32_t off_dt_strings; /*strings block的偏移地址*/
uint32_t off_mem_rsvmap; /*memory reservation block的偏移地址*/
uint32_t version; /*设备树版本信息*/
uint32_t last_comp_version; /*向后兼容的最低设备树版本信息*/
uint32_t boot_cpuid_phys; /*CPU ID*/
uint32_t size_dt_strings; /*strings block的大小*/
uint32_t size_dt_struct; /*structure block的大小*/
};
dtb 文件的结构图如下(jz2440.dts 设备树文件没有设置 /memreserve/内存信息):
(2)
reserve 保留,备用,预定
fdt_reserve_entry结构体如下:
struct fdt_reserve_entry {
uint64_t address; /*64bit 的地址*/
uint64_t size; /*保留的内存空间的大小*/
};
该结构体用于表示memreserve( 预留的内存区域)的起始地址和内存空间的大小,它紧跟在struct ftd_header结构体后面。例如:/memreserve/ 0x33000000 0x10000,fdt_reserve_entry 结构体的成员 address = 0x33000000,size = 0x10000。
(3)
structure block是用于描述设备树节点的结构,保存着节点的信息、节点的结构,它有5种标记类型:
① FDT_BEGIN_NODE (0x00000001):表示节点的开始,它的后面紧跟的是节点的名字;
② FDT_END_NODE (0x00000002):表示节点的结束;
③ FDT_PROP (0x00000003) :表示开始描述节点里面的一个属性,在FDT_PROP后面紧跟一个结构体如下所示:
struct {
uint32_t len; /*表示属性值的长度*/
uint32_t nameoff; /*属性的名字在string block的偏移*/
}
注:上面的这个结构体后紧跟着是属性值,属性的名字保存在字符串块(Strings block)中。
④ FDT_END (0x00000009):表示structure block的结束。
单个节点在structure block的存储格式如下图如所示:(注:子节点的存储格式也是一样)
总结:
(1) DTB文件可以分为四个部分:struct ftd_header、memory reservation block、structure block、strings block;
(2) 最开始的为struct ftd_header,包含其它三个部分的偏移地址;
(3) memory reservation block记录保留内存信息;
(4) structure block保存节点的信息,节点的结构;
(5) strings block保存属性的名字,将属性名字单独作为字符串保存;
编译生成dtb文件的源设备树jz2440.dts文件如下:
// 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/;
/ {
model = "SMDK2440";
compatible = "samsung,smdk2440";
#address-cells = <1>;
#size-cells = <1>;
memory@30000000 {
device_type = "memory";
reg = <0x30000000 0x4000000>;
};
chosen {
bootargs = "console=ttySAC0,115200 rw root=/dev/mtdblock4 rootfstype=yaffs2";
};
led {
compatible = "jz2440_led";
reg = <S3C2410_GPF(5) 1>;
};
};
jz2440.dtb 文件的内容如下:
接下来我们对应上图的编号逐一分析,
其中编号①~⑩表示的是fdt_header 结构体的成员信息:
① 对应 magic,表示设备树魔数,固定为0xd00dfeed;
② 对应 totalsize,表示整个设备设dtb文件的大小,从上图可知0x000001B9正好是dtb文件的大小441B;
③ 对应 off_dt_struct,表示structure block的偏移地址,为 0x00000038;
④ 对应off_dt_strings,表示 strings block的偏移地址,为 0x00000174;
⑤ 对应 off_mem_rsvmap;,表示memory reservation block的偏移地址,为 0x00000028;
⑥ 对应 version ,设备树版本的版本号为0x11;
⑦ 对应 last_comp_version,向下兼容版本号0x10;
⑧ 对应 boot_cpuid_phys,在多核处理器中用于启动的主cpu的物理id,为0x0;
⑨ 对应 size_dt_strings,strings block的大小为 0x45;
⑩ 对应 size_dt_struct,structure block的大小为 0x0000013C;
⑪~⑫ 对应结构体 fdt_reserve_entry ,它所在的地址为0x28,jz2440.dts 设备树文件没有设置 /memreserve/内存信息,所以address = 0x0,size = 0x0;
⑬ 所处的地址是0x38,它处在structure block中,0x00000001表示的是设备节点的开始;
⑭ 接着紧跟的是设备节点的名字,这里是根节点,所以为0x00000000;
⑮ 0x00000003 表示的是开始描述设备节点的一个属性;
⑯ 表示这个属性值的长度为0x09;
⑰ 表示这个属性的名字在strings block的偏移量是0,找到strings block的地址0x0174的地方,可知这个属性的名字是model;
⑱ 这个model属性的值是"SMDK2440",加上字符串的结束符NULL,刚好9个字节;