一、设备树的由来
Linux是世界上最大的开源项目之一,由全世界的内核开发者来维护,所有Linux爱好者都可以提交自己的代码进行审核。
Linux内核中有这么一部分是描述一个设备的文件,在早期linux内核下对各个平台/机器的硬件设备描述是使用.c文件(结构体)描述,他们充斥在arch/arm/plat-xxx和arch/arm/mach-xxx目录下。
但是由于每个平台上的硬件设备各有不同,这样对每个平台都需要一个.c文件来描述,随着arm平台越来越多,这样描述板级设备的文件就越多。
然而这对linux内核来说就是一些垃圾代码,(都是一些冗余的硬件描述代码),提交代码的目的是为了更新、优化内核,这些板级描述信息就显得没有意义。
于是为了方便管理将各个平台的硬件描述信息都放到/arch/arm/boot/dts/ 目录下用.dts的文件来描述硬件信息,这个文件采用树形结构来描述。那么 .dts 文件就是设备树。
二、dis、dtc和dtb的关系
dts是设备源码树(device tree sourse)是人能看懂的文件(字符文本文件),内核无法识别,所以使用dtc(Device Tree Compiler)这个编译工具来编译,得到内核可以识别的dtb(Device Tree binary )文件,这是一个二进制文件。
dts文件和dtb文件都在 /arch/arm/boot/dts/ 目录下。
dtc反汇编:
dtc -I dtb -O dts -o xxxx.dts xxxx.dtb
三、dts语法
dts是用于描述硬件平台信息,让内核知道一个设备上有那些外设,它们的地址、使用到哪些IO等。
设备树的结构就像树一样,它由若干个节点组成。
最上层的是根节点“/”,节点包括属性(属性就像变量,保存一些信息)和子节点。就这样若干个节点。
/dts-v1/; 文件开头的这个是设备树的版本。
注释: dts文件下注释方法和c一样,“/**/” 或 // 。
支持头文件:
支持.dtsi头文件和.h的头文件
值得注意的是dts文件也支持头文件,扩展名为.dtsi,用于描述一个soc的通用信息(比如cpu、soc、中断控制等等),当要用的时候直接引用这个.dtsi文件。这个头文件由soc厂商为我们提供。
比如我们想要使用正点原子阿尔法的开发板下的I2C外设与其它模块通信,那么我们的驱动就需要获取到该I2C外设的一些信息(时钟频率、器件地址、IO控制 等等),设备树就是用来描述这些信息。
我们知道阿尔法开发板使用的是I.MX6ULL这样一款soc,在I.MX6ULL的参考手册下可以找到I2C1是属于soc/AIPS-2/I2C1(这些是芯片厂商自己规定的),那么在对应I.MX6ULL就会有一个.dtsi的头文件来描述这样的信息,至于正点原子用它来做ap3216c这样一个实例则只要引用该设备树的头文件,采用追加的方式添加具体的属性就好了。引用头文件,追加具体的属性用‘&’符号。
前面SOC的外设控制器信息没必要重复描述,放头文件,所有使用该soc的板子都引用这个头文件,具体信息的独立描述,这也就是一个板子一个dts文件。
节点中主要对一些属性赋值,对于头文件中已经赋值过的节点,在dts文件中可以重新赋值。
设备树的节点
设备树由多个节点构成。
节点用于描述设备的硬件信息(用属性值来描述)。
一个设备树只有一个根节点,当引用头文件时,头文件中的根节点与dts文件中根节点是同一个节点(节点中的内容(子节点或属性)则是两个文件之和(比如有两个memory(内存)节点)。在头文件和dts文件中有重复赋值的属性,以最后的赋值为准。)dts文件中可以对头文件中的节点追加内容。
节点命名
节点名格式:node-name@unit-address;没有reg属性的后面没有单元地址。
比如I.MX6ULL的I2C4节点就是:I2C4@021F8000。I2C4是节点名 ;021F8000是单元地址 ,就是I2C4 寄存器的首地址 ,可以在I.MX6ULL参考手册中找到。
单元地址——一般就是外设寄存器的起始地址(不绝对),根据具体的节点而言。
标签
节点名前面有时候会有标签,使用‘:’号分隔,标签的意义是为了更好的找到节点,追加内容的时候通过‘&’符可以找到标签所在的节点(比如在dts文件在使用"&I2C4",这样追加,那么在头文件中就会有个节点前面有“I2C4:”标签修饰)。标签不属于节点名。
设备树在系统中的体现
bootloader在启动的时候会将设备树在内存中的地址作为参数传递给内核,所以内核能找到这些信息。内核会解析设备树(dtb文件),在/proc/device-tree目录下呈现。
根节点
系统启动后可以在根文件系统里面看到设备树的节点信息。在 /proc/device-tree目录下存放着根节点的属性和子节点 (节点属性以文件的形式存在/proc/device-tree目录下,子节点以文件夹的形式存在)。
soc节点
soc子节点下一般是一些外设控制的子节点,多与寄存器相关。
I2C控制器节点
如图I2C节点下是对I2C外设控制器的描述,如:#address-cells ,status,clock等等属性描述,还有在他下面的子节点
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
};
具体实例节点,如rtc
下图是对I2C外设的追加,status = “okay”,修改该status属性,使能I2C。rtc@32节点是对I2C外设使用的实例,用于rtc(实时时钟,因为RTC要与cpu通信)。rtc@32,这个32是rtc的器件地址。
注意:追加的是写在根节点外面的,通过“&标签名”追加。
特殊节点
aliases(别名)节点 和chosen 节点。
aliases节点
aliases一般是给节点定义别名用的(为了更好的找到节点),但是我们一般不用别名来访问,一般用标签的形式。内核会使用。
chosen节点
chosen 节点:主要目的是将uboot里面bootargs环境变量值,传递给内核作为命令行参数,cmd line。
在uboot中可以查看环境变量bootarges的值,同时在Linux启动时,会打印一行cmdline它的值和bootarges是一样的。
uboot是如何向内核传递bootarges的呢?
经过查看发现节点chosen中包含bootarges属性(我们并没有去写这个属性),它的值和uboot的bootarges是一致的。
那么chosen节点中bootarges是谁添加的呢?最有可能是uboot添加(拥有属性值(bootarges环境变量),而且对dtb操作过)。
uboot中有一个fdt_chosen函数会对chosen节点做一些修改,其中就有给bootarges属性赋值。
特殊属性
属性是节点中用来描述硬件信息,具体的属性由各个节点来决定(因为每个外设,硬件什么的特性都不一样)。
然而也会有一些约定熟成的属性:compatible(兼容的)属性、
compatible(兼容的)属性
设备节点下的compatible属性用于查找可用的驱动程序,根节点下的compatible属性用于匹配某个版本内核是否支持一个平台/机器。
设备节点下的compatible属性
compatible(兼容的)属性也叫 兼容性属性,值是字符串。它是用来描述兼容性的,在驱动程序中也会维护一个兼容性列表(数组结构体),在这个表里会列出它所支持的设备。通过设备树的compatible属性值,与驱动程序里的列表相匹配(字符串是不是一样),可以确定是否兼容。
compatible 属性值的格式:
自己随意赋值,只要是字符串,它的值可以有多个,也就是可以兼容多种驱动
根节点下的compatible属性
用于查看某个版本linux内核是否支持一个平台。
在没有设备树之前uboot会向内核传递一个machline_id值,内核查看是否支持此机器。
在使用设备树的时候,不使用机器ID而是使用根节点下的compatible属性。内核会维护一段空间来保存compatible属性(所有支持的机器)的值。
根节点下compatible值的格式:
“厂商,设备名” 值可以有多个(兼容多个平台的时候)
modle
它的值也是一个字符串,用来描述设备模块的信息,比如名字什么的,应该是平台名字。
modle = “wm8960-audio”;
status属性(相当于使能)
disabled表示不使用,okay表示可用。
#address-cells属性和#size-cells属性
它们的值是32位无符号整型数,可以用到任何一个子节点中,用于描述地址相关的信息。
#address-cells属性: 用于表示子节点reg属性中地址信息的单元数。
#size-cells属性: 用于表示子节点reg属性中长度(地址长度)信息的单元数。
地址信息和地址长度就表示了一段内存。
reg = <寄存器首地址 地址长度>
一个节点的reg属性里的数据会由它的父节点里的#address-cells属性和#size-cells属性决定。
#address-cells = <1>; //表示地址信息占一个单元
#size-cells = <1>; //地址长度也占一个单元
reg属性
前面提到了reg属性,**在大部分情况下reg属性是用来描述一段内存的。(I2C设备节点中reg是表示对方的器件地址,不是I2C控制器)**例如:
** reg = <寄存器首地址 地址长度> ** ,其中它里面的单元个数是由它的父节点的#address-cells属性和#size-cells属性决定的。
比如:
father{
#address-cells = <1>; //表示地址信息占一个单元
#size-cells = <1>; //地址长度也占一个单元
……
son@起始地址{
……
reg = <起始地址1 长度1>
};
};
father{
#address-cells = <2>; //表示地址信息占两个单元
#size-cells = <1>; //地址长度也占一个单元
……
son@起始地址{
reg = <起始地址1 起始地址2 长度1>
};
father{
#address-cells = <2>; //表示地址信息占两个单元
#size-cells = <2>; //地址长度也占两个单元
……
son@起始地址{
reg = <起始地址1 起始地址2 长度1 长度2> //这样来表示
};
};
之前说在大部分情况下用于描述一段内存,I2C最后与器件的描述就例外,它的reg用于描述与之通信的器件地址。
ranges属性
用于地址映射相关的,arm很少用到,在I.MX6ULL 中虽然有这个属性,但是值都是空的,表示不存在地址映射。
name属性和device_type属性
name属性:它的值是一个字符串,用来表示节点的名字。已经被淘汰,在老的平台上可能会看到。
device_type属性:也被淘汰,一般会用到cpu的属性和memory属性描述中。
绑定信息文档(相当于是设备树一些常见设备描述的参考文档)
描述一个设备的信息是用属性的,属性可以自定义,但是一些设备的属性大多类似(磁力计、陀螺仪传感器这一类属性大多通用),所以对于描述这样的设备都会遵循一个规则,这个规则在内核设备树的文档里面有写,叫绑定信息文档。
在 Documentation/devicetree/bindings/ 目录下有许多设备的绑定信息文档。
进入I2C外设这个文件夹,可以看到这些不同命名的.txt的文件,这其实是不同厂家芯片对与I2C这个外设的描述。
打开i2c-imx.txt 就可以看到恩智浦官方对I2C外设的描述:
* Freescale Inter IC (I2C) and High Speed Inter IC (HS-I2C) for i.MX
Required properties: //要求的属性
- compatible : //支持的驱动
- "fsl,imx1-i2c" for I2C compatible with the one integrated on i.MX1 SoC
- "fsl,imx21-i2c" for I2C compatible with the one integrated on i.MX21 SoC
- "fsl,vf610-i2c" for I2C compatible with the one integrated on Vybrid vf610 SoC
- reg : Should contain I2C/HS-I2C registers location and length //寄存器空间的描述
- interrupts : Should contain I2C/HS-I2C interrupt //中断的描述
- clocks : Should contain the I2C/HS-I2C clock specifier
Optional properties: //可选属性
- clock-frequency : Constains desired I2C/HS-I2C bus clock frequency in Hz. //时钟频率
The absence of the property indicates the default frequency 100 kHz.
- dmas: A list of two dma specifiers, one for each entry in dma-names.
- dma-names: should contain "tx" and "rx".
- scl-gpios: specify the gpio related to SCL pin
- sda-gpios: specify the gpio related to SDA pin
- pinctrl: add extra pinctrl to configure i2c pins to gpio function for i2c
bus recovery, call it "gpio" state
Examples:
i2c@83fc4000 { /* I2C2 on i.MX51 */ ****//示例****
compatible = "fsl,imx51-i2c", "fsl,imx21-i2c";
reg = <0x83fc4000 0x4000>;
interrupts = <63>;
};
i2c@70038000 { /* HS-I2C on i.MX51 */
compatible = "fsl,imx51-i2c", "fsl,imx21-i2c";
reg = <0x70038000 0x4000>;
interrupts = <64>;
clock-frequency = <400000>;
};
i2c0: i2c@40066000 { /* i2c0 on vf610 */
compatible = "fsl,vf610-i2c";
reg = <0x40066000 0x1000>;
interrupts =<0 71 0x04>;
dmas = <&edma0 0 50>,
<&edma0 0 51>;
dma-names = "rx","tx";
pinctrl-names = "default", "gpio";
pinctrl-0 = <&pinctrl_i2c1>;
pinctrl-1 = <&pinctrl_i2c1_gpio>;
scl-gpios = <&gpio5 26 GPIO_ACTIVE_HIGH>;
sda-gpios = <&gpio5 27 GPIO_ACTIVE_HIGH>;
};
~