linux设备树

一、概念
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中的一个节点,并向其中添加新的属性信息
linux设备树_第1张图片

(三)KEY
在设备树中,键值对是描述属性的方式,比如,Linux驱动中可以通过设备节点中的"compatible"这个属性查找设备节点。
Linux设备树语法中定义了一些具有规范意义的属性,包括:compatible, address, interrupt等,这些信息能够在内核初始化找到节点的时候,自动解析生成相应的设备信息。此外,还有一些Linux内核定义好的,一类设备通用的有默认意义的属性,这些属性一般不能被内核自动解析生成相应的设备信息,但是内核已经编写的相应的解析提取函数,常见的有 “mac_addr”,“gpio”,“clock”,“power”。“regulator” 等等。
1、compatible
设备节点中对应的节点信息已经被内核构造成struct platform_device。驱动可以通过相应的函数从中提取信息。compatible属性是用来查找节点的方法之一,另外还可以通过节点名或节点路径查找指定节点。
HDMI TX 驱动中就是使用下面这个函数通过设备节点中的"compatible"属性提取相应的信息,所以二者的字符串需要严格匹配。
linux设备树_第2张图片

从中可以发现这个驱动是使用的设备树描述的设备信息。我们可以找到它用来描述设备信息的结构体,可以看出,驱动中用于匹配的结构使用的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默认电平

你可能感兴趣的:(linux,驱动开发,运维)