1. 简介:
设备树:device tree,即设备构成的树
dts:device tree source,采用树形结构描述板级设备,即开发板上的设备信息,如cpu数量、内存基地址、iic接口上接了哪些设备、spi接口上……
dtb:将dts编译以后得到的二进制文件,uboot通过bootz或者bootm命令可以像linux内核传递这个二进制的.dtb
dtc:设备树编译工具,将dts编译成dtb,dtc工具源码在linux内核的scripts/dtc目录下,其makefile中说明依赖于dtc.c、flattree.c、fstree.c等文件,最终编译并链接出dtc这个主机文件,如果要编译dts文件的话只需要进入linux源码根目录下,然后执行make all(源码中所有东西)或者make dtbs(仅设备树文件)
2. dts语法:
该种文件常常时直接在soc厂商提供的.dts文件上进行修改,dts是一种asic文本文件,修改方便
2.1. dtsi头文件:
.dts文件是可以直接引用.dtsi和.h文件的,可以使用#include来引用.h或者.dtsi头文件
一般.dtsi文件用于描述soc的内部外设信息,比如cpu架构、主频、外设寄存器地址范围
2.2. 设备节点:
设备树采用树形结构来描述板子上的设备信息,每个设备都是一个节点,即设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键值对
2.2.1. 根节点:
“/”是根节点,每个设备树文件只有一个根节点,当两个dts文件各自都有一个“/”根节点之后,其实两个“/”根节点的内容会合并成一个根节点的
2.2.2. 子节点:
如cpu举例图,语法结构是:
2.2.3. node-name@unit-address
node-name是节点名字,为ascii字符串,节点名字应该能够清晰的描述节点的功能,比如“uart1”就表示这个节点是uart1外设
unit-address一般表示设备的地址或者寄存器首地址,如果某个节点没有地址或者寄存器的话,unit-address可以不要
当然还有其他格式,比如:
2.2.4. label:node-name@unit-address
label是为了方便访问节点,可以直接通过@label来访问这个节点
子节点也可以有自己的子节点
2.2.5. 其他数据结构:
字符串:“”
32位无符号整数:<>
字符串列表:“”,“”
2.3. 标准属性:
节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以自定义属性,当然了,很多属性是标准属性,linux下的很多外设驱动都会使用这些标准属性
2.3.1. compatible:
兼容性属性,其值是一个字符串列表,用于将设备和驱动绑起来,字符串列表用于选择设备所要使用的驱动程序,格式如下:
“manufacturers,model”
manufacturers表示厂商,model一般是模块对应的驱动名字
假设属性值有两个,那么这个设备首先使用第一个兼容值在linux内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有则使用第二个兼容值
一般驱动文件都会有一个OF匹配表,保存着一些compatible值,如果设备节点的compatible属性值和OF匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动
比如在[model].c里面就会这样定义:(model在这里是imx_wm8960)这就是OF匹配表
2.3.2. model:
这个属性值也是一个字符串,用来描述设备模块信息,就是名字啥的
2.3.3. status:
也是字符串,是设备状态信息:
2.3.4. #address-cells和#size-cells:
这俩都是无符号32位整形,二者可以用在任何拥有子节点的设备中,用以描述子节点的地址信息,前者决定子节点reg属性中地址信息所占用的字长(32位),后者决定了子节点reg属性中长度信息所占的字长(32位)
2.3.5. reg:
一般是(address,length)对,用以描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围
2.3.6. ranges:
可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵,是一个地址映射/转换表,range属性每个项目由子弟之、附地址和地址空间长度三部分组成:
child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells确定此物理地址所占用的字长
parent-bus-address:父总线地址空间的物理地址,同样由父节点的#address-cells确定此物理地址所占用的字长
length:子弟之空间长度,由父节点的#size-dells确定此地址长度所占用的字长
如果ranges属性值为空,说明子地址空间和附地址空间完全相同,不需要进行地址转换
2.3.7. name:
字符串,用于记录节点名字,多被弃用,不必管,只一些老设备需要
2.3.8. device_type:
字符串,ieee1275会用到这个属性,用以描述设备FCode,设备树没得FCode就不管了
2.4. 根节点compatible属性:
根节点的该属性结构和其他节点的一样,第一个值描述了所使用的硬件设备名字,第二个值描述设备所使用的soc,linux内核会通过根节点的该属性查看是否支持此设备,如果支持的话设备就会启动linux内核
2.4.1. 使用设备树之前的匹配方法:
没有设备树的啥时候,uboot会向linux内核传递一个叫做machine id的值,即设备ID,高速linux内核自己是什么设备,看看linux是否支持
linux内核是支持很多设备的,针对每一个设备(板子),linux内核都用MACHINE_START和MACHINE_END来定义一个machine_desc结构体来描述这个设备
2.4.2. 使用设备树之后的设备匹配方法:
有了设备树之后,不用上面的宏了,而是换了DT_MACHINE_START:
他的结构跟上面的差不多,只是.nr不一样了,此处该值为~0,即引入设备树之后不会再根据machine id来检查linux内核是否支持某个设备了
这就是匹配表了
对于如何匹配这个表(根据设备树根节点的compatible属性来匹配出对应的machine_desc),是这样的:
linux内核调用start_kernel函数来启动内核,start_kernel函数会调用setup_arch函数来匹配machine_desc,举个例子:
调用setup_machine_fdt函数来获取匹配的machine_desc,参数就是atags的首部地址,也就是uboot传递给linux内核的dtb文件首地址,其返回值就是找到最匹配的machine_desc,定义如下:
再调用of_flat_dt_match_machine(),来获取machine_desc
整个流程如下所示:
2.5. 向节点追加或修改内容:
产品的开发会涉及频繁需求变更,比如iic换了,那么一旦硬件改了,我们就要改设备树文件
如果新加了一个硬件,那么最简单的就是在原本该类硬件的节点下直接添加一个子节点
如果是追加或者修改呢,可以公国&label来访问节点,即需要写个引用,&谁就代表要引用谁,不会影响到其他使用此设备树的板子
3. 创建小型模板设备树:
一般就是在soc厂商提供的.dts上面修改,主要做的是:
3.1. 添加根节点:
在根节点下添加兼容表
3.2. 添加cpus节点和soc节点:
/ { compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull"; cpus { #address-cells = <1>; #size-cells = <0>; //CPU0节点 cpu0: cpu@0 { compatible = "arm,cortex-a7"; device_type = "cpu"; reg = <0>; }; }; //soc节点 soc { #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; ranges; } } |
3.3. 添加存储器节点和其他外设节点:
存储器节点可以这样搞:
外设节点涉及到层级关系的,需要用子节点关系来搞
3.4. 设备树在系统中的体现:
在文件系统中
/proc/device-tree/ 或 /sys/firmware/devicetree/base/下都会有设备树的影子(根节点“/”的所有属性和其子节点)
如果想查看根节点的子节点的子节点的话,那就进入对应的目录就可以查看了
3.5. 特殊节点:
根节点中有两个特殊子节点,一个是aliases,一个是chosen
3.5.1. aliases子节点:
意思就是“别名”,也就是在这里就是起个名字,当然了,一般在节点命名的时候会加上label的,然后通过&label来访问节点,这里一个意思
3.5.2. chosen子节点:
这不是一个真实的设备,chosen节点主要是为了uboot向linux内核传递数据,重点是bootargs参数,内容就像这样子:
如果用了这个,那就可以找到目录下有bootargs这些了,bootargs里面是参数传递的依据。
在内核代码中也有fdt_chosen函数,大概流程如下:
4. linux内核解释dtb文件
linux内核在启动的时候会解析dtb文件,然后再/proc/device-tree目录下生成相应的设备树节点文件,过程如下所示:
5. 绑定信息文档:
在设备树中添加一个硬件对应的节点需要查相关的说明,在linux内核源码中就有详细的.txt文档描述如何添加节点,即绑定文档,路径为/Documentation/devicetree/bindings,如果看不懂,就位fae吧
6. 设备树常用的OF操作函数:
设备树描述了设备的详细信息,包括数字类型、字符串类型、数组类型的,我们再编写驱动的时候需要获取到这些写信息,就通过一系列的“of_”前缀的函数来获取设备树中的节点或者属性信息,即OF函数,当然了,所匹配的逻辑上的表就是OF表
6.1. 查找节点的of函数:
设备都是以设备节点的形式“挂”到设备树上,通过获取到这个设备的节点来获取这个设备的其他属性
折叠源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
6.2. 查找父/子节点的of函数:
折叠源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|