一、概述
ARM Device Tree起源于OpenFirmware (OF),在Linux 2.6中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不过是垃圾,如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件platform_data。常见的s3c2410、s3c6410等板级目录,代码量在数万行。
Linus Torvalds对于此种情况大发雷霆,在2011年的ARM Linux邮件列表宣称this whole ARM thing is a f*cking pain in the ass”。
所以Linux开发社区就开始整改,设备树最早用于PowerPC等其他体系架构,ARM架构开发社区就开始采用设备树来描述设备的信息。
二、设备树结构
Device Tree是一种描述硬件的数据结构,由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中):CPU的数量和类别,内存基地址和大小,总线和桥,外设连接,中断控制器和中断使用情况,GPIO控制器和GPIO使用情况,Clock控制器和Clock使用情况。
通常由.dts文件以文本方式对系统设备树进行描述,经过Device Tree Compiler(dtc)将dts文件转换成二进制文件binary device tree blob(dtb),.dtb文件可由Linux内核解析,有了device tree就可以在不改动Linux内核的情况下,对不同的平台实现无差异的支持,只需更换相应的dts文件,即可满足。
设备树信息被保存在一个ASCII 文本文件中,适合人类的阅读习惯,类似于xml文件, 在ARM Linux中,一个.dts文件对应一个ARM的machine放置在内核的arch/arm/boot/dts/目录。
三、设备树语法
3.1 节点node
3.2 属性property
属性拥有两种格式:
Property格式1(没有值) : [label:] property-name;
Property格式2(键值对) : [label:] property-name = value;;
四、设备树实例
下述例子译自: http://elinux.org/Device_Tree_Usage
下面给出一个例子,从0开始描述如何写一个完整的设备树。现在有个arm平台的板子,假设制造商为“acme”我们给他命名为“Coyote's Revenge”首先,假设我们有如下的硬件平台:
一颗 32bit ARM CPU
处理器本地总线上映射了串口SPI控制器、I2C控制器、中断控制器以及外部总线桥。
基地址为0,大小为256MB的SDRAM
基地址分别为0x101F1000 和0x101F2000的2个串口
基地址为0x101F3000 GPIO控制器
基地址为0x10170000的SPI 控制器上有如下的设备:
MMC 卡槽的SS引脚连接到了GPIO #1
外部总线桥上有如下的设备:
SMC公司生产的SMC91111 Ethernet,其基地址为0x10100000。
在基地址为0x10160000的 i2c 控制器有如下设备:
美信公司的DS1338 实时时钟芯片. 从站相应地址为0x58
大小为64MB 的 NOR flash 其基地址为0x30000000
下面将按照上面的描述信息,写一个DTS文件。
初始化结构体
首先先给整个设备树写一个框架如下所示:
/dts-v1/;
/ {
compatible ="acme,coyotes-revenge";
};
上面首先在根节点下写了一个属性compatible,该属性是系统用来识别不同板级设备的重要依据,其一般由厂商名和样板名两部分组成,比如在上面我们的制造商为acme板子的名字叫做coyotes-revenge。
加入CPU
假如我们的CPU是一颗双核A9的CPU,我们添加一个叫”cpus”的子节点,具体如下:
/dts-v1/;
/ {
compatible ="acme,coyotes-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
};
cpu@1 {
compatible = "arm,cortex-a9";
};
};
};
cpu的compatible设置与顶层的类似,有两部分组成。Arm描述了制造商,cortex-a9指明了型号。
系统中每个设备都是依靠设备树节点来描述的,接下来就要添加一些设备节点来描述每一个设备:
/dts-v1/;
/ {
compatible = "acme,coyotes-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
};
cpu@1 {
compatible = "arm,cortex-a9";
};
};
serial@101F0000{
compatible = "arm,pl011";
};
serial@101F2000 {
compatible = "arm,pl011";
};
gpio@101F3000 {
compatible = "arm,pl061";
};
interrupt-controller@10140000 {
compatible = "arm,pl190";
};
spi@10115000 {
compatible = "arm,pl022";
};
external-bus {
ethernet@0,0 {
compatible = "smc,smc91c111";
};
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
rtc@58 {
compatible ="maxim,ds1338";
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm","cfi-flash";
};
};
};
通过上面的列表可以看出来,通过dts的层次关系,可以看到具体的板级的设备连接关系,比如外部总线上有ethercat、i2c和flash三个设备。
但是上面描述的这课树还不是一个完整有效的设备树,因此他缺少必要的连接方式、地址信息等。稍后会加上这些信息。
理解Compatible
每个设备节点都会有compatible属性, 设备与驱动之间的结合就依赖这个属性的匹配。
Compatible属性是由字符串列表组成,就像上面列出的flash节点,compatible有两个属性值,以一个字符串确切的表示该设备的信息,第二个字符串描述的与该节点描述符相兼容的设备。
设备如何寻址
设备寻址地址在设备树中通过如下的属性信息来表示:
reg
address-cells
size-cells
每一个可寻址设都会有一个reg属性,该属性有一个或多个元素组成,其基本格式为:
reg =<address1 length1 [address2 length2] [address3 length3] ... >
上面的每一个元素都代表设备的寻址地址及其寻址大小,每一个元素中的address值可以是一个或者多个无符号32位整形数据类型cell来表示,元素中的length可以为空也可以使一个或者多个无符号32位整形数据类型cell。
由于每个可寻址设备都会有reg属性可设置,而且reg属性元素也是灵活可选择的,那么谁来制定reg属性元素中每个元素也就是address和length的个数呢?
在这里,要关注到期父节点的两个属性,其中#address-cells表示reg中address元素的个数,#size-cells用来表示length元素的个数。
为了展示刚刚接手实行的作用,那么现在做一个演示,首先从cpu节点开始演示:
CPU 寻址地址
对于寻址地址的编写,cpu节点是最简单的一个例子,之前介绍,而每个cpu节点都包含一个标记ID,但是没有其他的描述信息,这里填充一些其他的属性:
cpus {
#address-cells= <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a9";
reg= <0>;
};
cpu@1 {
compatible = "arm,cortex-a9";
reg= <1>;
};
};
在cpus节点 #address-cells 赋予 1, #size-cells 赋予 0.。这意味着在其子节点的reg只有一个地址元素值,没有长度元素值。在这个案例当中,两颗cpu核的地址分别呗分配成0和1。因为每个cpu只分配了地址,所以节点 #size-cells元素被设置为 0。
内存映射设备
需要内存映射的设备不同于上面的cpu节点,这类的设备需要一段内存而不是单一的内存地址,因此不近需要包含内存的基地址还而且还需要映射地址的长度,因此需要使用 #size-cells属性来表示reg属性元素中表示地址长度元素的个数。在下面的例子中,每一个节点的address值有一个32位无符号整形数据而且length值也是用一个32位无符号整形数据来表示。因此在32的系统中 #address-cells 和#size-cells都要设置为1,但是在64位系统中 #address-cells就要设置成2了。具体设置如下:
/dts-v1/;
/ {
#address-cells= <1>;
#size-cells = <1>;
...
serial@101f0000{
compatible= "arm,pl011";
reg =<0x101f0000 0x1000 >;
};
serial@101f2000{
compatible= "arm,pl011";
reg =<0x101f2000 0x1000 >;
};
gpio@101f3000 {
compatible= "arm,pl061";
reg =<0x101f3000 0x1000
0x101f4000 0x0010>;
};
interrupt-controller@10140000 {
compatible= "arm,pl190";
reg =<0x10140000 0x1000 >;
};
spi@10115000 {
compatible= "arm,pl022";
reg =<0x10115000 0x1000 >;
};
...
上面的例子中reg属性都有address元素和length属性,值的注意的是,例子中的GPIO被分配了两个地址范围,分别是 0x101f3000...0x101f3fff 以及0x101f4000..0x101f400f。
有一些设备可能有不同的寻址方案,比如一个设备挂载到总线上连接一个片选信号线,可以通过片选信号选择不同的设备。由于父节点可以定义了其子节点的地址映射域,所以可以选择最适合的一项来描述硬件设备。下面的代码就是把片选号码编入地址码挂载到外部总线上的一个设备。
external-bus {
#address-cells= <2>
#size-cells = <1>;
ethernet@0,0 {
compatible = "smc,smc91c111";
reg= <0 0 0x1000>;
};
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
reg = <1 0 0x1000>;
rtc@58{
compatible = "maxim,ds1338";
};
};
flash@2,0 {
compatible= "samsung,k8f1315ebm", "cfi-flash";
compatible= <2 0 0x4000000>;
};
};
上面的代码中, #address-cells 属性为2,则表示reg属性的address有两个地址域,其中一个表示片选号,另一个表示设备到片选基地址的偏移量,#size-cells为1,其地址范围量的个数还是一个32位的无符号整数。所以最后reg有三个属性值,分别表示片选号、偏移量、地址范围。
无内存映射设备
其他的一些设备,他们在处理器总线瓶没有内存映射。他们拥有地址范围但是他们不被cpu直接的访问,而是被父设备驱动替代cpu进行访问。
举个例子,对于i2c设备,每个设备都会有一个指定的访问地址,但是这些设备不会有相关联的范围或者地址长度,这有点类似cpu节点地址分配。如下是具体代码的例子:
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
#address-cells= <1>;
#size-cells = <0>;
reg =<1 0 0x1000>;
rtc@58{
compatible = "maxim,ds1338";
reg= <58>;
};
};
参考:
https://blog.csdn.net/sgmenghuo/article/details/45071615
https://blog.csdn.net/woshidahuaidan2011/article/details/52948732
设备树使用文档