设备树的概念
设备树就是以树的方式组织描述板级设备信息,比如CPU数量、内存基地址、IIC接口上面挂接了哪些设备、SPI接口上挂接了哪些设备等等。
一般 .dts描述板级信息 (也就是开发板上有哪些 IIC设备、 SPI设备等 ),.dtsi描述 SOC级信息 (也就是 SOC有几个 CPU、主频是多少、各个外设控制器信息等 )。
DTS、DTB和DTC
DTS是设备树源码文件, DTB是将DTS编译以后得到的二进制文件。
将 .c文件编译为 .o需要用到 gcc编译器,那么将 .dts编译为 .dtb需要什么工具呢?需要用到 DTC工具! DTC工具源码在 Linux内核的 scripts/dtc目录下,scripts/dtc/Makefile文件是生成DTC工具的编译管理脚本。
如果要编译 DTS文件的话只需要进入到 Linux源码根目录下,然后执行如下命令:
make all
或者:
make dtbs
make all”命令是编译 Linux源码中的所有东西,包括 zImage,.ko驱动模块以及设备树,如果只是编译设备树的话建议使用“ make dtbs”命令。
如果我们使用I.MX6ULL新做了一个板子,只需要新建一个此板子对应的文件,然后将对应的文件,然后将对应的.dtb文件名添加到dtb-$(CONFIG_SOC_IMX6ULL)下,这样在编译设备树的时候就会将对应的.dts编译为二进制的.dtb文件。
DTS语法
dtsi头文件
和 C语言一样,设备树也支持头文件,设备树的头文件扩展名为 .dtsi。
一般 .dtsi文件用于描述 SOC的内部外设信息,比如CPU架构、主频、外设寄存器地址范围,比如 UART、IIC等等。比如 imx6ull.dtsi就是描述 I.MX6ULL这颗 SOC内部外设情况信息的。
设备节点
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键值对。
设备树的命名格式为node-name@unit-address
其中“ node-name”是节点名字,为 ASCII字符串,节点名字应该能够清晰的描述出节点的功能,比如“ uart1”就表示这个节点是 UART1外设。“ unit-address”一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话“ unit-address”可以不要,比如 cpu@0”、
interrupt-controller@00a01000”。
另一种命名引入label,格式为label: node-name@unit-address,引入label的目的就是为了方便访问节点,可以直接通过 &label来访问这个节点,比如通过&cpu0就可以访问“ cpu@0”这个节点,而不需要输入完整的节点名字。再比如节点 intc: interrupt-controller@00a01000”,节点 label是 intc,而节点名字就很长了,为 interrupt-controller@00a01000”。很明显通过 &intc来访问“ interrupt-controller@00a01000”这个节点要方
便很多!
a.字符串:compatible = “arm,cortex-a7”; //代码设置 compatible属性的值为字符串“ arm,cortex-a7”。
b.32 位无符号整数
reg = <0>; //代码设置 reg属性的值为 0 reg的值也可以设置为一组值,比如:reg = <0 0x123456 100>;
c.字符串列表
属性值也可以为字符串列表,字符串和字符串之间采用 “,”隔开,compatible = “fsl,imx6ull-gpmi-nand”, “fsl, imx6ul-gpmi-nand”; //代码设置属性 compatible的值为“ fsl,imx6ull-gpmi-nand”和 fsl, imx6ul-gpmi-nand”。
标准属性
节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以自定义属性。除了用户自定义属性,有很多属性是标准属性, Linux下的很多外设驱动都会使用这些标准属性。
1.compatible属性
compatible属性也叫做“兼容性”属性,这是非常重要的一个属性!compatible属性的值是一个字符串列表,compatible属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序, compatible属性的值格式为:
“manufacturer,model” 其中 manufacturer表示厂商, model一般是模块对应的驱动名字。比如 imx6ull-alientek-emmc.dts中 sound节点是 I.MX6U-ALPHA开发板的音频设备节点,I.MX6U-ALPHA开发板上的音频芯片采用的欧胜 (WOLFSON)出品的WM8960 sound节点的compatible属性值如下:
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内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查。一般驱动程序文件都会有一个 OF匹配表,此 OF匹配表保存着一些 compatible值,如果设备节点的 compatible属性值和OF匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。
2.model属性
model属性值也是一个字符串,一般 model属性描述设备模块信息,比如名字什么的,比如:model = “wm8960-audio”;
3.status属性
status属性看名字就知道是和设备状态有关的, status属性值也是字符串,字符串是设备的状态信息,可选的状态如下表所示:
4.#address-cells和 #size-cells属性
这两个属性的值都是无符号 32位整形,#address-cells和 #size-cells这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。#address-cells属性值决定了子节点 reg属性中地址信息所占用的字长 (32位 ),#size-cells属性值决定了子节点 reg属性中长度信息所占的字长 (32位 )。 #address-cells和 #size-cells表明了子节点应该如何编写 reg属性值,一般reg属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度,reg属性的格式一为:
reg = <address1 length1 address2 length2 address3 length3…………>
每个“ address length”组合表示一个地址范围,其中 address是起始地址,length是地址长度,#address-cells表明 address这个数据所占用的字长,#size-cells表明 length这个数据所占用的字长。
#address-cells = <1>;//说明子节点 reg属性中起始地址所占用的字长为1;
#size-cells = <0>;//说明地址长度所占用的字长为 0。
5.reg属性
reg属性的值一般是 (address length)对。reg属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息.
6.ranges属性
ranges属性值可以为空或者按照 (child-bus-address,parent-bus-address,length)格式编写的数字矩阵,ranges是一个地址映射 /转换表,ranges属性每个项目由子地址、父地址和地址空间长度
这三部分组成:
child-bus-address:子总线地址空间的物理地址,由父节点的 #address-cells确定此物理地址所占用的字长。
parent-bus-address 父总线地址空间的物理地址,同样由父节点的 #address-cells确定此物理地址所占用的字长。
length子地址空间的长度,由父节点的 #size-cells确定此地址长度所占用的字长。
如果 ranges属性值为空值,说明子地址空间和父地 址空间完全相同,不需要进行地址转换,对于我们所使用的 I.MX6ULL来说,子地址空间和父地址空间完全相同,因此会在 imx6ull.dtsi中找到大量的值为空的 ranges属性。
7.name属性
name属性值为字符串, name属性用于记录节点名字, name属性已经被弃用,不推荐使用name属性,一些老的设备树文件可能会使用此属性。
8.device_type属性
device_type属性值为字符串, IEEE 1275会用到此属性,用于描述设备的 FCode,但是设备树没有 FCode,所以此属性也被抛弃了。此属性只能用于 cpu节点或者 memory节点。
特殊节点
1.aliases节点
单词aliases的意思是“别名”,因此aliases节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。不过我们一般会在节点命名的时候会加上label然后通过&label来访问节点,这样也很方便,而且设备树里面大量的使用&label来访问节点。
2.chosen节点
chosen并不是一个真实的设备, chosen节点主要是为了 uboot向 Linux内核传递数据,重点是 bootargs参数。一般 .dts文件中 chosen节点通常为空或者内容很少, imx6ul.dtsi中 chosen节点内容如下所示:
chosen {
stdout-path = &uart1;
};
从示例代码 中可以看出,chosen节点仅仅设置了属性“ stdout-path”,表示标准输出使用 uart1。但是当我们进入到 /proc/device-tree/chosen目录里面,会发现多了 bootargs这个属性,如图所示:
输入 cat命令查看 bootargs这个文件的内容,查看之后发现bootargs的值就是我们设置启动的参数,那么这个参数是怎么写入到设备树的chonsen节点的呢?
答案就是uboot自己在节点里面添加了bootargs属性!并且设置bootargs属性的值为环境变量的值。因为在启动Linux内核之前,只有uboot知道环境变量的值,并且uboot也知道.dtb设备树文件在DRAM中的位置。
uboot中的fdt_chosen函数在设备树的chosen节点中加入了bootargs属性,并且还设置了bootargs属性值,整个流程如下:
上图框起来的部分就是函数do_bootm_linux 函数的执行流程,也就是说do_bootm_linux 函数会通过一系列复杂的调用,最终通过dt_chosen 函数在chosen 节点中加入了bootargs 属性。而我们通过bootz 命令启动Linux 内核的时候会运行do_bootm_linux 函数,至此,真相大白,一切事情的源头都源于如下命令:
bootz 80800000 – 83000000
当我们输入上述命令并执行以后,do_bootz 函数就会执行,然后一切就按照上图中所示的流程开始运行。
Linux内核解析DTB文件
Linux 内核在启动的时候会解析DTB 文件,然后在/proc/device-tree 目录下生成相应的设备树节点文件。Linux内核解析DTB 文件的流程如图所示:
从上图中可以看出,在start_kernel 函数中完成了设备树节点解析的工作,最终实际工作的函数为unflatten_dt_node。