目录
③reg属性、#address-cells 和#size-cells 属性
关于设备树历史由来可以自行百度
一、文件
1、DTS
将这些描述板级硬件信息的内容都从 Linux 内中分离开来,用一个专属的文件格式来描述,这个专属的文件就叫做设备树(Device Tree),扩展名为.dts。这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU 数量、 内存基地址、 IIC 接口上接了哪些设备、 SPI 接口上接了哪些设备等
2、DTSI
一个 SOC 可以作出很多不同的板子,这些不同的板子肯定是有共同的信息, 将这些共同的信息提取出来作为一个通用的文件,其他的.dts 文件直接引用这个通用文件即可,这个通用文件就是.dtsi 文件,类似于 C 语言中的头文件。一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如 UART、 IIC 等
3、DTB
DTS 是设备树源码文件, DTB 是DTS 编译以后得到的二进制文件
4、DTC
.将dts 编译为.dtb的工具
四者关系
dtsi是通用的”头文件“,有同一个soc的共同信息;dts是采用树形结构的具体的描述板级设备源码;
用dtc工具把dts文件编译后得到dtb文件
二、编译命令
如果要编译 DTS 文件的话需要进入到 Linux 源码根目录下,然后执行如下命令:
make all 或 make dtbs
“make all”命令是编译 Linux 源码中的所有东西,包括 zImage, .ko 驱动模块以及设备树
如果只是编译设备树的话建议使用“make dtbs”命令
三、新建dtb
如果使用 I.MX6ULL 新做了一个开发板,只需要新建一个开发板对应的.dts 文件
打开 arch/arm/boot/dts/Makefile
将对应的.dtb 文件名添加到 dtb-$(CONFIG_SOC_IMX6ULL)下,这样在编译设备树的时候就会将对应的.dts 编译为二进制的.dtb文件,在移植的时候有具体的操作,这里不再过多描述
四、DTS语法
1 、.dtsi 头文件
和 C 语言一样,设备树也支持头文件,设备树的头文件扩展名为.dtsi
这里 .dts 文件引用 C 语言中的.h 文件,甚至也可以引用.dts 文件
比如,打开 imx6ull-14x14-evk-gpmi-weim.dts
这直接引用了.dts 文件,因此在.dts 设备树文件中,可以通过“#include”来引用.h、 .dtsi 和.dts 文件。在编写设备树头文件的时候最好选择.dtsi 后缀
imx6ull.dtsi 是描述 I.MX6ULL 这颗 SOC 内部外设情况信息的,部分如下
图中cpu0 这个设备节点信息,这个节点信息描述了I.MX6ULL 这颗 SOC 所使用的 CPU 信息,比如架构是 cortex-A7,频率支持 996MHz、 792MHz、528MHz、396MHz 和 198MHz 等等。在 imx6ull.dtsi 文件中不仅仅描述了 cpu0 这一个节点信息,I.MX6ULL 这颗 SOC 所有的外设都描述的清清楚楚
2、设备节点
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息
比如,imx6ull.dtsi 文件中缩减出来的设备树文件内容如下
1 / {
2 aliases {
3 can0 = &flexcan1;
4 };
5
6 cpus {
7 #address-cells = <1>;
8 #size-cells = <0>;
9
10 cpu0: cpu@0 {
11 compatible = "arm,cortex-a7";
12 device_type = "cpu";
13 reg = <0>;
14 };
15 };
16
17 intc: interrupt-controller@00a01000 {
18 compatible = "arm,cortex-a7-gic";
19 #interrupt-cells = <3>;
20 interrupt-controller;
21 reg = <0x00a01000 0x1000>,
22 <0x00a02000 0x100>;
23 };
24 }
第 1 行,“/”是根节点,每个设备树文件只有一个根节点。 imx6ull.dtsi和 imx6ull-alientek-emmc.dts 这两个文件都有一个“/”根节点,这两个“/”根节点的内容会合并成一个根节点
2、 6 和 17 行, aliases、 cpus 和 intc 是三个子节点,在设备树中节点命名格式如下:
node-name@unit-address
“node-name”是节点名字,为 ASCII 字符串,节点名字应该能够清晰的描述出节点的功能,比如“uart1”就表示这个节点是 UART1 外设。“unit-address”一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要,比如“interrupt-controller@00a01000”
和“cpu@0”,还有一种是添加了标签,用“:”隔开成了两部分,“:”前面的是节点标签(label),“:”后面的是节点名字,标签格式如下
label: node-name@unit-address
引入 label 的目的就是为了方便访问节点,可以直接通过&label 来访问这个节点,比如通过
&cpu0 就可以访问“cpu@0”这个节点,而不需要输入完整的节点名字,有些节点名字是很长的
第 10 行, cpu0 也是一个节点,只是 cpu0 是 cpus 的子节点。每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流
数据形式
设备树源码中常用的几种数据形式如下所示:
①、字符串
compatible = "arm,cortex-a7";设置 compatible 属性的值为字符串“arm,cortex-a7”
②、 32 位无符号整数
reg = <0>;设置 reg 属性的值为 0
reg = <0 0x123456 100>;设置为一组值
③、字符串列表
compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand"
属性值也可以为字符串列表,字符串和字符串之间采用“,”隔开
属性 compatible 的值为“fsl,imx6ull-gpmi-nand”和“fsl, imx6ul-gpmi-nand”
3、向节点追加或修改内容
产品开发过程中可能面临着频繁的需求更改,一旦硬件修改了,就要同步的修改设备树文件,毕竟设备树是描述板子硬件信息的文件
假设现在有个六轴芯片fxls8471, fxls8471 要接到 I.MX6U-ALPHA 开发板的 I2C1 接口上,那么相当于需要在 i2c1 这个节点上添加一个 fxls8471 子节点
找开发板使用的设备树文件.dts,需要在.dts 文件中完成数据追加的内容,方式如下:
1 &i2c1 {
2 /* 要追加或修改的内容 */
3 };
第 1 行, &i2c1 表示要访问 i2c1 这个 label标签所对应的节点,也就是 imx6ull.dtsi 中的“i2c1:i2c@021a0000”,即会访问下图代码
第 2 行,花括号内就是要向 i2c1 这个节点添加的内容,包括修改某些属性的值
向节点追加或修改内容,重点就是通过&label 来访问节点,然后在里面编写要追加或者修改的内容
4、标准属性
节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以自定义属性。除了用户自定义属性,有很多属性是标准属性, Linux 下的很多外设驱动都会使用这些标准属性
①compatible 属性和model 属性
compatible 属性也叫做“兼容性”属性,这是非常重要的一个属性! compatible 属性的值是一个字符串列表,model 属性是一个字符串, compatible 属性用于将设备和驱动绑定起来,一般 model 属性描述设备模块信息。字符串列表用于选择设备所要使用的驱动程序, compatible 属性值和model 属性值格式如下所示:
"manufacturer,model" manufacturer 表示厂商, model 一般是模块对应的驱动名字
比如sound 节点的compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";
属性值有两个,分别为“fsl,imx6ul-evk-wm8960”和“fsl,imx-audio-wm8960”,其中“fsl”表示厂商是飞思卡尔,“imx6ul-evk-wm8960”和“imx-audio-wm8960”表示驱动模块名字。 sound这个设备首先使用第一个兼容值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查
②status 属性
status 属性是和设备状态有关的, status 属性值也是字符串,字符串是设备的状态信息
③reg属性、#address-cells 和#size-cells 属性
这两个属性的值都是无符号 32 位整形, #address-cells 和#size-cells 这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息
reg前两个值是address的,后一个值是size的,它受#address-cells<>和#size-cells<>需要控制
再看,这是<2>和<0>,那么reg的两个值都是address的,size没有
一般 reg 属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度, reg 属性的格式一为:reg = <address1 length1 address2 length2 address3 length3……>
address 是起始地址, length 是地址长度, #address-cells 表明 address 这个数据所占用的字长, #size-cells 表明 length 这个数据所占用的字长
④ranges 属性
ranges属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字
矩阵, ranges 是一个地址映射/转换表, ranges 属性每个项目由子地址、父地址和地址空间长度
这三部分组成:
child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长
parent-bus-address: 父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物
理地址所占用的字长。
length: 子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长
比如reg = <0x4600 0x100>;ranges = <0x0 0xe0000000 0x00100000>;
serial 设备可以从 0xe0004600 开始进行读写操作,0xe0004600=0x4600+0xe0000000
5、特殊属性
每个节点都有 compatible 属性,根节点“/”也有,.dts 文件中根节点的 compatible 属性内容如:
通过根节点的 compatible 属性可以知道所使用的设备,一般第一个值描述了所使用的硬件设备名字,比如这里使用的是“imx6ull-14x14-evk”这个设备,第二个值描述了设备所使用的 SOC,比如这里使用的是“imx6ull”这颗 SOC。 Linux 内核会通过根节点的 compoatible 属性查看是否支持此设备,如果支持的话设备就会启动 Linux 内核。
如果将 imx6ull-alientek-emmc.dts 根节点的 compatible 属性改为其他的值,Linux 内核无法启动
五、设备树在系统中的体现
Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/devicetree 目录下根据节点名字创建不同文件夹
1、根节点“/”各个属性
图中的“#address-cells”、“#size-cells”、“compatible”、“model”和“name”这 5 个文件,它们在设备树中就是根节点的 5个属性。既然是文件那么肯定可以查看其内容,输入cat 命令来查看 model
和 compatible 这两个文件的内容
对应文件 imx6ull-alientek-emmc.dts如下
2、根节点“/”各子节点
各个文件夹(图中粗字体文件夹)就是根节点“/”的各个子节点,比如“aliases”、“ backlight”、“chosen”和“ clocks”等。/proc/device-tree 目录就是设备树在根文件系统中的体现,同样是按照树形结构组织的,进入/proc/device-tree/soc 目录中就可以看到 soc 节点的所有子节点
对应这些属性文件的内容和 imx6ull.dtsi 中 soc 节点的属性值相同
其他的文件与文件夹都同理
六、特殊节点
在根节点“/”中有两个特殊的子节点: aliases 和 chosen
1、aliases 子节点
打开 imx6ull.dtsi 文件, aliases 节点内容如下
Linux启动的时候会扫描该文件,单词 aliases 的意思是“别名”,因此 aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。比如soc有很多个i2c控制器,aliases就相当于给每个i2c控制器分配一个唯一的编号。比如i2c1=&i2c2,也就是通过i2c1访问i2c2,那么给编号就是1,可以在/dev下看到名为i2c-1的设备节点
2、chosen 子节点
chosen 并不是一个真实的设备, chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。
uboot 在启动 Linux 内核的时候会将 bootargs 的值传递给 Linux内核, bootargs 会作为 Linux 内核的命令行参数, Linux 内核启动的时候会打印出命令行参数(也就是 uboot 传递进来的 bootargs 的值)
.dts 中 chosen 节点内容如下所示
这里没有bootargs 属性,说明chosen 节点的 bootargs 属性不是在设备树里面设置的,那么只有一种可能,那就是 uboot 自己在 chosen 节点里面添加了 bootargs 属性!并且设置 bootargs 属性的值为 bootargs环境变量的值。因为在启动 Linux 内核之前,只有 uboot 知道 bootargs 环境变量的值,并且uboot也知道.dtb 设备树文件在 DRAM 中的位置。
在 uboot 源码中全局搜索“ chosen”这个字符串,fdt_support.c 文件中有个 fdt_chosen 函数,uboot 中的 fdt_chosen 函数在设备树的 chosen 节点中加入 bootargs属性,并且还设置bootargs 属性值
do_bootm_linux 函数会通过一系列复杂的调用,最终通过 fdt_chosen 函数在 chosen 节点中加入
了 bootargs 属性。而我们通过 bootz 命令启动 Linux 内核的时候会运行 do_bootm_linux 函数
七、绑定信息文档
设备树是用来描述板子上的设备信息的,不同的设备其信息不同,反映到设备树中就是属性不同。在Linux 内核源码中有详细的.txt 文档描述了如何添加节点,这些.txt 文档叫做绑定文档,路径为:Linux 源码目录/Documentation/devicetree/bindings
比如现在要想在 I.MX6ULL 这颗 SOC 的 I2C 下添加一个节点,那么就可以查看
Documentation/devicetree/bindings/i2c/i2c-imx.txt,此文档详细的描述了 I.MX 系列的 SOC 如何
在设备树中添加 I2C 设备节点。有时候使用的一些芯片在 Documentation/devicetree/bindings 目录下找不到对应的文档,这个时候就要咨询芯片的提供商,让他们给提供参考的设备树文件