一、概念
Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离。在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。引入了设备树之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。
一个.dts(device tree source)文件对应一个machine,一般放置在内核的"arch/arm/boot/dts/"目录内
可以通过make dtbs命令编译成二进制的.dtb文件供内核驱动使用。
由于一个系统可能对应多个machine,如果每个machine的设备树都写成一个完全独立的.dts文件,那么势必相当一些.dts文件有重复的部分,为了解决这个问题,Linux设备树目录把多个machine共同的部分提炼为相应的.dtsi文件。这样每个.dts就只有自己差异的部分,公有的部分只需要"include"相应的.dtsi文件,
这样就是整个设备树的管理更加有序。
二、设备树框架
设备树用树状结构描述设备信息,它有以下几种特性
1、每个设备树文件都有一个根节点,每个设备都是一个节点。
2、节点间可以嵌套,形成父子关系,这样就可以方便的描述设备间的关系。
3、每个设备的属性都用一组key-value对(键值对)来描述。
4、每个属性的描述用“;”结束。
所以,一个设备树的基本框架可以写成下面这个样子,一般来说,/表示板子,它的子节点node1表示系统上的某个控制器,控制器中的子节点node2表示挂接在这个控制器上的设备(们)。如下所示:
/{ //根节点
node1{ //node1是节点名,是/的子节点
key=value; //node1的属性
…
node2{ //node2是node1的子节点
key=value; //node2的属性 … }
} //node1的描述到此为止
node3{ key=value; … } }
三、设备树知识点
(一)节点名
理论个节点名只要是长度不超过31个字符的ASCII字符串即可,Linux内核还约定设备名应写成形如[@unit_address]的形式,其中name就是设备名,最长可以是31个字符长度。unit_address一般是设备地址,用来唯一标识一个节点.
(二)引用
当我们找一个节点的时候,我们必须书写完整的节点路径,这样当一个节点嵌套比较深的时候就不是很方便,所以,设备树允许我们用下面的形式为节点标注引用(起别名),借以省去冗长的路径。这样就可以实现类似函数调用的效果。使用引用可以避免移植者四处找节点,直接在板级.dts增改即可。
下面的例子中就是直接引用了dtsi中的一个节点,并向其中添加新的属性信息
(三)KEY
在设备树中,键值对是描述属性的方式,比如,Linux驱动中可以通过设备节点中的"compatible"这个属性查找设备节点。
Linux设备树语法中定义了一些具有规范意义的属性,包括:compatible, address,
interrupt等,这些信息能够在内核初始化找到节点的时候,自动解析生成相应的设备信息。此外,还有一些Linux内核定义好的,一类设备通用的有默认意义的属性,这些属性一般不能被内核自动解析生成相应的设备信息,但是内核已经编写的相应的解析提取函数,常见的有
“mac_addr”,“gpio”,“clock”,“power”。“regulator” 等等。
1、compatible
设备节点中对应的节点信息已经被内核构造成struct
platform_device。驱动可以通过相应的函数从中提取信息。compatible属性是用来查找节点的方法之一,另外还可以通过节点名或节点路径查找指定节点。
HDMI TX 驱动中就是使用下面这个函数通过设备节点中的"compatible"属性提取相应的信息,所以二者的字符串需要严格匹配。
从中可以发现这个驱动是使用的设备树描述的设备信息。我们可以找到它用来描述设备信息的结构体,可以看出,驱动中用于匹配的结构使用的compatible和设备树中一模一样,否则就可能无法匹配,这里另外的一点是struct
of_device_id数组的最后一个成员一定是空,因为相关的操作API会读取这个数组直到遇到一个空。
2、address
几乎所有的设备都需要与CPU的IO口相连,所以其IO端口信息就需要在设备节点中说明。常用的属性有
#address-cells = <1>; 基地址、片选号等绝对起始地址所占字长(32位)
#size-cells = <1>; 长度所占字长(32位)
cpus { 1. #address-cells = <1>; 2. #size-cells = <0>; 3. cpu@0 { 4. compatible
= "arm,cortex-a9"; 5. reg = <0>; 6. }; 7. cpu@1 { 8. compatible =
"arm,cortex-a9"; 9. reg = <1>; 10. }; };
#address-cells 设置为 1,#size-cells 设置为 0。这意味着子节点的 reg 值是一个单一的
uint32,这是一个不包含大小字段的地址,为这两个 cpu 分配的地址是 0 和 1。cpu 节点的 #size-cells 为 0 是因为只为每个 cpu
分配一个单独的地址。
1.#address-cells = <1>; 2. #size-cells = <1>; 3. 4. ... 5. 6. serial@101f0000 {
7. compatible = "arm,pl011"; 8. reg = <0x101f0000 0x1000 >; 9. }; 10. 11.
serial@101f2000 { 12. compatible = "arm,pl011"; 13. reg = <0x101f2000 0x1000 >;
14. }; 15. 16. gpio@101f3000 { 17. compatible = "arm,pl061"; 18. reg = <
0x101f3000 0x1000 19. 0x101f4000 0x0010>; 20. };
每个设备都被分配了一个基址以及该区域的大小。这个例子中为 GPIO 分配了两个地址范围:0x101f3000…0x101f3fff 和
0x101f4000…0x101f400f。
1.#address-cells = <2> 2. #size-cells = <1>; 3. 4. ethernet@0,0 { 5. compatible
= "smc,smc91c111"; 6. reg = <0 0 0x1000>; 7. };
外部总线的地址值使用了两个 cell,一个用于片选号;另一个则用于片选基址的偏移量。而长度字段则还是单个
cell,这是因为只有地址的偏移部分才需要一个范围量。所以,在这个例子中,每个 reg 项都有三个 cell:片选号、偏移量和长度。
3、GPIO
对于GPIO控制器而言,它提供如下属性:
gpio-controller 表明自己的身份为gpio控制器
#gpio-cells 该属性与#address-cells相似,它声明了gpio指示符cell的个数,
一般 gpio控制器 #gpio-cells = <2> 第一个cell表示gpio号,第二个cell表示gpio默认电平