ARM 设备树
的起源
设备树
是一种描述硬件的数据结构
,它起源于OpenFirmware。在linux2.6中,ARM架构的板级硬件细节过多的被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx中,采用设备树后,很多硬件信息可以直接通过设备树传递给linux,而不需要在内核中进行大量的冗余编码。- 设备树由一系列被命名的
节点(Node)
和属性(Property)
组成,而节点本身可以包含子节点
。所谓属性
,其实就是成对出现
的名称和值
。在设备树中,可描述的信息
包括:- CPU的数量和类别
- 内存基地址和大小
- 总线和桥
- 外设连接
- 中断控制器和中断使用情况
- GPIO控制器和GPIO使用情况
- 时钟控制器和时钟使用情况
- 设备树基本上就是画一棵电路板上的CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核,然后内核识别这棵树,并根据设备树展开linux内科中的
platform_device、i2c_client、spi_device
等设备,而这些设备用到的内存、IRQ
等资源也被传递给了内核,内核会将这些资源绑定
给展开的相应的设备。
一、设备树的组成和结构
- 整个设备树牵涉面比较广,增加了新的用于描述设备硬件信息的
文本格式
,又增加了编译这个文本的工具
,同时Bootloader也需要支持将编译后的设备树传递给linux内核。
1.1 DTS
-
文件
.dts
是一种ASCII文件格式
的设备树描述,此文本格式非常人性化,适合人类的阅读习惯。基本上,在ARM liunx中,一个.dts
对应一个ARM的设备,一般放置在内核的arch/arm/boot/dts
目录中,DTS绝不是ARM的专利,在其他的目录下也存在大量的.dts
文件。 -
一个
SOC
可能对应多个
设备(一个SOC可以对应对个产品和电路板),这些.dts
文件势必会包含许多共同的部分,linux内核为了简化,把SOC公用的部分一般提炼为.dtsi
,类似于C语言的头文件。其他的设备对应的.dts
就包括这个.dtsi
。- 例如
vexpress-v2m.dtsi
就被vexpress-v2p.dts
所引用,在vexpress-v2p.dts
中有:/include/ "vexpress-v2m.dtsi"
- 例如
-
和C语言的头文件类似,
.dtsi
也可以包含其他的.dtsi
-
文件
.dts
的基本元素为节点
和属性
,如下是一个设备树结构的模板。
/{
node1 {
a-string-property = "A string";
a-string-list-property = "first string","second string";
a-byte-data-property = {0x01 0x23 0x34 0x56};
child-node1{
first-child-property;
second-child-property = <1>;
a-string-property = "Hello,World";
};
child-node2{
};
};
node2 {
an-empty-property;
a-cell-property = <1 2 3 4>;
child-node1{
};
};
};
- 上述的
.dts
文件没有任何其他用途,但是它基本的表征了一个设备树源文件的结构。- 1个
root
节点"/"
; - root节点下面含一系列子节点,本例中为
node1和node2
; - 节点node1下又含有一系列的子节点,本例中为
child-node1和child-node2
; - 各节点又有一系列的属性,这些属性
- 可能为空:
an-empty-property
; - 可能为字符串:
a-string-property
; - 可能为字符串数组:
a-string-list-property
; - 可能为Cells(由u32整数组成):
second-child-property
; - 可能为二进制数:
a-byte-data-property
。
- 可能为空:
- 1个
1.2 DTC
DTC
是将.dts
文件编译为.dtb
文件的工具。DTC的源代码位于内核的scripts/dtc
目录中,在linux内核使能了设备树的情况下,编译内核的时候主机工具DTC会被编译出来,对应于scripts/dtc/Makefile
中"hostprogs-y := dtc"
这一hostprogs
的编译目标。- DTC也可以在ubuntu中单独安装。
- 在linux内核
arch/arm/boot/dts/Makefile
中,描述了当某种SOC被选中后,哪些.dtb
文件会被编译出来。 - DTC除了可以编译
.dts
文件以外,还可以反汇编.dtb文件为.dts文件
。- 命令格式:
./scripts/dtc/dtc -I dtb -O dts -o xxx.dts arch/arm/boot/dts/xxx.dtb
- 命令格式:
1.3 DTB
- 文件
.dtb
是.dts
被DTC编译后的二进制格式的设备树描述,可由linux内核解析,当然U-Boot这样的bootloader也可以识别.dtb
的。 - 在为电路板制作
NAND、SD启动映像
时,会为.dtb
文件留下单独的区域存放,之后bootloader
在引导内核时,会先读取该.dtb
到内存。
1.4 绑定(Binding)
- 对于设备树中的节点和属性具体是如何来描述设备的硬件细节的,一般需要文档来进行讲解,文档的后缀名为
.txt
,在这个文件中,需要描述对应节点的兼容性、必需的属性和可选的属性。 - 这些
.txt
文件位于内核的Document/devicetree/bindings
目录下,其下又分为很多子目录。- 设备树
绑定文档
的主要内容
包括:- 关于该模块的最
基本的描述
必需属性
(Required Properties)可选属性
(Optional Property)- 一个
实例
(Example)
- 关于该模块的最
- 设备树
- linux内核下的
scripts/checkpatch.pl
会运行一个检查,如果在设备树中添加
了新的compatible字符串
,而没有添加对应的文档进行解释,checkpatch
程序会报出警告:UNDOCUMENTED_DT_STRINGDT compatible string xxx appears un-documented
。
1.5 Bootloader
Uboot
设备从v1.1.3
开始支持设备树- 为了使能设备树,需要在编译Uboot的时候在
config
文件中加入:#define CONFIG_OF_LIBFDT
- 在Uboot中,可以从NAND、SD或者TFTP等介质中将
.dtb
读入内存。- 假设
.dtb
放入的地址为0x71000000
,之后可以在Uboot运行fdt addr
命令设置.dtb
的地址,如:
UBoot> fdt addr 0x71000000
fdt
其他命令就变得可以使用,如:fdt resize
、fdt print
…
- 假设
二、根节点兼容性
- 在
.dts
文件中,兼容属性:compatible = "acme,coyotes-revenge";
- 定义了整个系统的名称(设备级别),它的组织形式是:
<manufacturer>,<model>
。
- linux内核通过
根节点
的兼容属性
可以判断
它启动的是什么设备
。在真实的项目中,这个顶层设备的兼容属性,一般包括两个或者两个以上
的兼容性字符串,首个兼容性字符串是板子级别
的名字,后一个兼容性是芯片级别(
或者芯片系列
的名字)的名字。 - 譬如板子
arch/arm/boot/dts/vexpress-v2p-ca9.dts
兼容于arm,vexpress,v2p-ca9
和arm,vexpress
:compatible = "arm,vexpress,v2p-ca9","arm,vexpress";
- 譬如板子
arch/arm/boot/dts/vexpress-v2p-ca5s.dts
的兼容性为:compatible = "arm,vexpress,v2p-ca5s","arm,vexpress";
- 譬如板子
arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts
的兼容性为:compatible = "arm,vexpress,v2p-ca15_a7","arm,vexpress";
- 可以看出,上述各个电路板的共性是兼容
arm,vexpress
,而特性是分别兼容与"arm,vexpress,v2p-ca9"
,"arm,vexpress,v2p-ca5s"
和"arm,vexpress,v2p-ca15_a7"
。 - 在
linux2.6
内核中,ARM linux针对不同的电路板会建立由MACHINE_START
和MACHINE_END
包围起来的针对这个设备的一系列回调函数。这些不同的设备,会有不同的MACHINE ID,Uboot
在启动内核时,会将MAHCINE ID存放在r1寄存器
,linux启动时会匹配
Bootloader传递的MACHINE ID和MACHINE_START申明的MACHINE ID,然后再执行一系列的初始化函数
, - 在
linux 3.x
引入设备树之后,MACHINE_START
变更为DT_MACHINE_START
,其中含有一个.dt_compat
成员,用于表明相关的设备与.dts
中根节点的兼容属性兼容关系。如果Bootloader
传递给内核的设备树根节点中的兼容属性出现在某设备的.dt_compat
表中,相关的设备就对应匹配,从而引发这一设备的一系列的初始化函数。- 示例见书
《linux设备驱动开发详解:基于最新的linux 4.0内核》P469
- 示例见书
三、设备节点兼容性
- 在
.dts
文件的每个设备节点中,都有一个兼容属性,兼容属性用于驱动和设备的绑定
。 - 兼容属性是一个字符串的列表,列表的第一个字符串表征了节点代表的确切设备,形式为
"<manufacturer>,<model>"
,其后的字符串表征可兼容的其他设备。可以说前面是特指,后面是涵盖更广的范围。 - 使用设备树后,驱动需要与
.dts
中描述的设备节点进行匹配,从而使驱动的probe()函数执行。对于platform_driver
而言,需要添加一个OF
(operation_file)匹配表。
四、设备节点及Label
的命名
node-name@unit-address
unit-address
一般都是外设寄存器的起始地址,有时候是I2C的设备地址,或者是其他含义,具体节点具体分析。
- 设备树里面经常遇到如下节点名字:
intc:interrupt-controller@00a01000
- '
:
'前面是标签label,后面才是名字
- '
- 可以通过
&label
的形式进行访问这个label,这种引用是通过phandle(pointer handle)
进行的。
五、地址编码
- 可寻址的设备使用如下信息在设备树中编码地址信息:
reg #address-cells #size-cells
- 其中
reg
的组织形式为reg=<address1 length1 [address2 length2] [address3 length3]...>
,其中的每一组address length
表明了设备使用的地址范围
。 address
为一个或者多个32位的整型(cell)
,而length
意味着从address到address+length-1
的地址范围都属于该节点。若#size-cells = 0
,则length
字段为空
。address
和length
字段可变长,父节点的#address-cells和#size-cells
分别决定了子节点的reg属性
的address和length
字段长度。
- 其中