[架构之路-34]:目标系统 - 系统软件 - Linux OS硬件电路的文本描述:设备树的构成属性解析使用

Posted 文火冰糖的硅基工坊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[架构之路-34]:目标系统 - 系统软件 - Linux OS硬件电路的文本描述:设备树的构成属性解析使用相关的知识,希望对你有一定的参考价值。

目录

第1章 什么是树

1.1 树形结构

1.2 无处不在的树形结构

1.3 树形结构的好处

第2章 硬件系统设备树

2.1 什么是硬件设备树

2.2 Linux引入设备树的好处

2.3 Linux什么时候引入设备树与设备

2.4 Linux设备树的外观

第3章 Linux设备树的工作原理

3.1 Linux内核驱动的设备树

3.2 硬件电路设备树描述文件

3.3 uboot如何把硬件电路设备树传递给Linux内核

3.4 xxx.dts的组织与结构

第4章 硬件电路设备树描述文件的实例

4.1 imx6dl-hummingboard.dts文件

4.2 imx6dl.dtsi文件

4.3 实例2

第5章 硬件电路设备树描述文件的语法解析

5.1 节点或子节点或分支描述:

5.2 设备节点的地址属性:Reg

5.3 设备节点的地址段的位数:#address-cells

5.4 设备节点地址段的位数:#size-cells 

5.5 设备驱动程序兼容性属性:compatible

5.6 设备所属的软件模块名属性:model

5.7 设备节点的使能状态属性:status

5.8 地址映射属性:ranges

5.9 别名属性

5.10 中断属性:interrupts

5.11 子节点:xxx

第6章 设备节点的中断与SOC的中断控制器


第1章 什么是树

1.1 树形结构

树形结构指的是数据元素之间存在着“一对多”的树形关系的数据结构,是一类重要的非线性数据结构。

树形结构是一层次的嵌套结构。

在树形结构中,树根结点没有前驱结点,其余每个结点有且只有一个前驱结点。

叶子结点没有后续结点,其余每个结点的后续节点数可以是一个也可以是多个。

一个树形结构的外层和内层有相似的结构,所以这种结构多可以递归的表示。

经典数据结构中的各种树状图是一种典型的树形结构:一棵树可以简单的表示为根,左子树,右子树。左子树和右子树又有自己的子树。

1.2 无处不在的树形结构

(1)大自然

(2)公司的的组织架构

(3)Linux的文件系统的组织架构

1.3 树形结构的好处

(1)树型结构是组织各种“资源”、构建一个系统非常有效的一种手段。

(2)树形结构非常容易扩展,可以动态的增加、插入、删除任意节点和分支。

(3)符合大脑的思维方式,思维导图就是一个树形结构。

(4)使用范围广:几乎可以应用在任何领域,包括各种抽象概念。

(5)层次分明:树形结构可以展现鲜明的层次关系。

第2章 硬件系统设备树

2.1 什么是硬件设备树

用树型的结构和文本的方式来描述硬件电路的各种元器件或设备的组成和相互关系,这就是设备树。

2.2 Linux引入设备树的好处

从电路板布局的角度来看,ARM架构与X86的架构最大的区别,就是采用ARM的嵌入式系统硬件电路板千差万别,而X86的PC机的硬件电路板数量和种类相对较少。

Linux支持各种CPU架构的硬件电路,他们是如何区分不同的硬件电路板呢?如何区分不同硬件电路板内部的硬件设备的组成与关系?以及硬件设备的参数,如内存的大小和起始地址?SOC芯片不同控制器设备的初始地址?数量?这些信息,直接影响硬件驱动程序的编写和执行?

在Linux 2.6以及之前的版本, ARM架构的板极硬件细节过多地被硬编码在C语言的源代码中,相应的源代码被安置在arch/arm/plat-xxx和arch/arm/mach-xxx中,大量的#define宏定义,定义了不同板子的不同设备的参数。这就导致,硬件电路的细节、参数发生变化后,都需要重新编译内核程序或内核设备驱动程序,而这些变化,实际上并没有影响内核和设备驱动程序的实际逻辑,只是数据或参数发生了变化而已。这些代码都需要与Linux一起发布,这导致Linux与硬件绑定越来越紧,Linux越来越丧失适应性和硬件无关性,这导致Linux的创始人对ARM很是不满,并且大骂ARM的硬件厂家提供的解决方案。考虑到Linux应用的广泛性和Linux创始人的个人威望,于是引起了ARM硬件厂家的高度重视。强烈需要一种新的硬件电路板的配置与Linux驱动程序分离的机制。设备树机制因运而生,设备树并不是ARM的独创,PowerPC架构的CPU先前已经采用,效果不错。ARM以此为基础,并在此基础上进一步的优化,从而定义了一个事实上的硬件配置与Linux软件分离的标准。

采用设备树后,许多硬件的细节可以直接通过设备树的形式传递给Linux,而不再需要在内核中进行大量的冗余编码,也不需要在源代码中通过#define定义大量的硬件相关的信息。Linux内核驱动程序从传入的设备树数据中获取硬件的相关信息,如地址信息,长度信息等。

2.3 Linux什么时候引入设备树与设备

Linux内核从3.x开始正式引入设备树的概念,用于实现驱动代码与硬件设备信息相分离。

2.4 Linux设备树的外观

“/":表示根节点。

cpus: 多核CPU

第3章 Linux设备树的工作原理

3.1 Linux内核驱动的设备树

除了由电路板厂家提供的设备树描述文件外,内核的的硬件设备驱动程序,也有相应的数据结构描述硬件电路的组成。只不过,内核数据结构中的参数值,要来自外部的来自设备厂家提供的设备描述文件。

3.2 硬件电路设备树描述文件

Linux为设备树定义了三种文件:

(1)xxx..dts:device tree source,设备树源描述文件

该文件不是c语言代码,而是一种文本描述文件。每个硬件开发板,都有自己的xxx.dts文件,一般放置在内核的"arch/arm/boot/dts/xxx.dts"目录内。

(2)xxx.DTB:device tree binary,设备树二进制文件

.dtb文件是 .dts 被 DTC 编译后的二进制格式的设备树文件,它可以被linux内核解析。

(3)DTC: device tree compile,设备树编译器

DTC是将.dts编译为.dtb的工具,相当于gcc。

3.3 uboot如何把硬件电路设备树传递给Linux内核

(1)编译

有两种方式可以编译xxx..dts:

  • 用工具单独编译DTC文件
  • 在编译uboot的时候,把xxx.DTB打包到uboot中。

(2)load到内存

  • uboot在启动的时候,把xxx.DTB load到自己的内存空间中

(3)传递给kernel

  • 通过boot arg参数,把内存中的xxx.DTB的地址传递给内核

(4)Kernel解析DTB,并映射到内部设备驱动树

内核在获取到设备树二进制文件xxx.DTB的地址后,解析该二进制中的结构化数据,并把相关的信息保存到内核驱动程序的设备树中。

3.4 xxx.dts的组织与结构

由于一个SoC芯片可能对应多一个硬件电路板,即不同的硬件电路板有相同的SOC芯片以及SOC芯片内部的控制器,但拥有不同的SOC外部电路。为了支持在不同的电路板之间共享相同的SOC以及其内部电路描述,Linux设备树描述文件支持类似C语言的Include机制,把一些共同的部分提炼为一个xxx .dtsi 文件。每个SOC芯片内部的电路描述就可以定义成xxx .dtsi 文件,该文件描述了SOC芯片内部的硬件设备的连接关系。

而使用该SOC的电路板的硬件描述文件,就可以直接include该xxx .dtsi,这样就不需要重复定义。

从而,可以通过include的方式,组织一个更大范围的电路板的描述文件。

另外,被#include的文件,还可以进一步的嵌套其他的设备描述文件。

因此,1个dts文件+n个dtsi文件,它们编译而成的dtb文件就是真正的设备树。

soc厂商会把soc公共的特性和多块开发板公用的特性提炼为dtsi,而dts则负责描述某个具体的产品(开发板)的特性。

dts直接或间接的包含多个dtsi(类似于c语言的头文件),就体现了一个完整的产品(开发板)所有的特性。以solidrun公司的hummingboard为例,其组成关系示例如下:

imx6dl-hummingboard.dts
        |_imx6dl.dtsi
        |   |_imx6qdl.dtsi
        |_imx6qdl-microsom.dtsi
        |_imx6qdl-microsom-ar8035.dtsi

第4章 硬件电路设备树描述文件的实例

下面分别是是imx6dl-hummingboard.dts以及imx6dl.dtsi文件,我们以它们为例来分析

不难发现dts文件内容很少,只有一些板级的特征,大部分公共的硬件描述都在dtsi文件中。

4.1 imx6dl-hummingboard.dts文件

/dts-v1/;                                           /* 指定dts的版本号*/
#include "imx6dl.dtsi"                              /* 包含其他公共设备描述*/
#include "imx6qdl-microsom.dtsi"
#include "imx6qdl-microsom-ar8035.dtsi"

/                                                  /* 根节点 */
    model = "SolidRun HummingBoard DL/Solo";
    compatible = "solidrun,hummingboard", "fsl,imx6dl";

    ir_recv: ir-receiver 
        compatible = "gpio-ir-receiver";
        gpios = <&gpio1 2 1>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_hummingboard_gpio1_2>;
    ;

    regulators 
        compatible = "simple-bus";

        reg_3p3v: 3p3v 
            compatible = "regulator-fixed";
            regulator-name = "3P3V";
            regulator-min-microvolt = <3300000>;
            regulator-max-microvolt = <3300000>;
            regulator-always-on;
        ;
    

&i2c1 
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_hummingboard_i2c1>;

    rtc: pcf8523@68 
        compatible = "nxp,pcf8523";
        reg = <0x68>;
    ;
;
  

4.2 imx6dl.dtsi文件

/ 
    aliases 

    /*省略无关代码*/
    
    soc 
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus";
        interrupt-parent = <&intc>;
        ranges;

        /*省略无关代码*/

        timer@00a00600 
            compatible = "arm,cortex-a9-twd-timer";
            reg = <0x00a00600 0x20>;
            interrupts = <1 13 0xf01>;
            clocks = <&clks IMX6QDL_CLK_TWD>;
        ;

        aips-bus@02000000  /* AIPS1 */
            compatible = "fsl,aips-bus", "simple-bus";
            #address-cells = <1>;
            #size-cells = <1>;
            reg = <0x02000000 0x100000>;
            ranges;

            /*省略无关代码*/      

            gpio1: gpio@0209c000 
                compatible = "fsl,imx6q-gpio", "fsl,imx35-gpio";
                reg = <0x0209c000 0x4000>;
                interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>,
                         <0 67 IRQ_TYPE_LEVEL_HIGH>;
                gpio-controller;
                #gpio-cells = <2>;
                interrupt-controller;
                #interrupt-cells = <2>;
            ;

            /*省略无关代码*/  

            i2c1: i2c@021a0000 
                #address-cells = <1>;
                #size-cells = <0>;
                compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
                reg = <0x021a0000 0x4000>;
                interrupts = <0 36 IRQ_TYPE_LEVEL_HIGH>;
                clocks = <&clks IMX6QDL_CLK_I2C1>;
                status = "disabled";
            ;

        ;
            /*省略无关代码*/  
    ;  
;

4.3 实例2

    /   
        compatible = "acme,coyotes-revenge";  
        #address-cells = <1>;  
        #size-cells = <1>;  
        interrupt-parent = <&intc>;  
      
        cpus   
            #address-cells = <1>;  
            #size-cells = <0>;  
            cpu@0   
                compatible = "arm,cortex-a9";  
                reg = <0>;  
            ;  
            cpu@1   
                compatible = "arm,cortex-a9";  
                reg = <1>;  
            ;  
        ;  
      
        serial@101f0000   
            compatible = "arm,pl011";  
            reg = <0x101f0000 0x1000 >;  
            interrupts = < 1 0 >;  
        ;  
      
        serial@101f2000   
            compatible = "arm,pl011";  
            reg = <0x101f2000 0x1000 >;  
            interrupts = < 2 0 >;  
        ;  
      
        gpio@101f3000   
            compatible = "arm,pl061";  
            reg = <0x101f3000 0x1000  
                   0x101f4000 0x0010>;  
            interrupts = < 3 0 >;  
        ;  
      
        intc: interrupt-controller@10140000   
            compatible = "arm,pl190";  
            reg = <0x10140000 0x1000 >;  
            interrupt-controller;  
            #interrupt-cells = <2>;  
        ;  
      
        spi@10115000   
            compatible = "arm,pl022";  
            reg = <0x10115000 0x1000 >;  
            interrupts = < 4 0 >;  
        ;  
      
        external-bus   
            #address-cells = <2>  
            #size-cells = <1>;  
            ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet  
                      1 0  0x10160000   0x10000     // Chipselect 2, i2c controller  
                      2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash  
      
            ethernet@0,0   
                compatible = "smc,smc91c111";  
                reg = <0 0 0x1000>;  
                interrupts = < 5 2 >;  
            ;  
      
            i2c@1,0   
                compatible = "acme,a1234-i2c-bus";  
                #address-cells = <1>;  
                #size-cells = <0>;  
                reg = <1 0 0x1000>;  
                interrupts = < 6 2 >;  
                rtc@58   
                    compatible = "maxim,ds1338";  
                    reg = <58>;  
                    interrupts = < 7 3 >;  
                ;  
            ;  
      
            flash@2,0   
                compatible = "samsung,k8f1315ebm", "cfi-flash";  
                reg = <2 0 0x4000000>;  
            ;  
        ;  
    ; 

第5章 硬件电路设备树描述文件的语法解析

5.1 节点或子节点或分支描述:

包围起来的结构称之为一个节点或一个分支.

(1)根节点, 节点名为"/"

dts中最开头的 /

                           

(2)其他节点, 节点名为“nodeName 

nodeName 

       

nodeName 为ASCII字符串,节点名字应该能够清晰的描述出节点的功能,比如“uart1”就表示这个节点是UART1外设;

(3)指定内存寄存器地址的节点, 节点名为“nodeName@unit-address”

nodeName@unit-address

       

其中,unit-address表示节点的内存地址或编号

如果某个节点没有地址或者寄存器的话 “unit-address” 可以不要。

在上图中:cpu节点和 ethernet节点依靠不同的unit-address 分辨不同的CPU;

可见,node-name相同的情况下,可以通过不同的unit-address定义不同的设备节点。

5.2 设备节点的地址属性:Reg

reg = <address域  length域>

虽然,可以直接在节点名称后面,直接通过@指明节点设备在地址空间中的位置。

但大多数时候,可以通过reg属性名,来指明该节点设备在地址空间中起始位置和地址长度。

案例:

reg = <0x021a0000 0x4000>;

节点设备的内存地址为0x021a0000,长度为0x4000

<address 和length> 对的个数是可变的,由该节点点的属性#address-cells和#size-cells 决定。

5.3 设备节点的地址段的位数:#address-cells

该域指明:用多少个32bit来表示内存空间的地址。32总线时,该值为1;64位总线时,该值为2。

5.4 设备节点地址段的位数:#size-cells 

该域指明:用多少个32bit来表示内存空间的长度。32总线时,该值为1;64位总线时,该值为2。

5.5 设备驱动程序兼容性属性:compatible

compatible 属性值为字符串列表,⽤于将设备和兼容的驱动程序绑定起来。

字符串列表⽤于选择设备所要使用的驱动程序名称。

"manufacturer,model"     //anufacturer :厂商  model:模块对应的驱动名

一般设备驱动程序文件中都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。

5.6 设备所属的软件模块名属性:model

model 属性:描述设备模块信息,比如名字什么的,如:model = “wm8960-audio”。

5.7 设备节点的使能状态属性:status

status 属性:描述设备状态,如:okay - 设备可操作,disabled - 设备不可操作。

5.8 地址映射属性:ranges

ranges它是一个地址映射/转换表。

如果 ranges 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换。

5.9 别名属性

用 aliases 节点给多个同类型的控制器分配唯一编号(别名),便于Linux内核区分。

在Linux启动时会解析aliases节点。

5.10 中断属性:interrupts

该属性定义了设备节点挂接到哪个全局中断号上,需要为该设备挂接中断服务程序。

(1)带两个参数的情形

    interrupt-parent = <&gpio2>;
    interrupts = <5 1>;

表示中断控制器是GPIO2, 使用GPIO2的第5号中断,

5:该设备使用GPIO作为其中断,中断号为5

1:代表中断的触发方式,这里是指中断触发的方式为上升沿触发,

  • 1:上升沿触发
  • 2:下降沿触发
  • 3:高电平触发
  • 4:低电平触发

详见内核代码中的头文件include/dt-bindings/interrupt-controller/irq.h

(2)带三个参数的情形

interrupts = <GIC_SPI 66 1>;

中断的类型:  

IPI:CPU内部的中断号,inter-processer interrupt   中断号0~15
PPI:CPU core之间的终端号,per processor interrupts    中断号16~31
SPI:所有CPU共享的中断号,shared processor interrupts  中断号  32 ~32 + 224
SGI:软中断,software generated interrupts (SGI).

所以上述示例表示中断类型为共享处理器中断(SPI),

中断号为SPI中断类型中的第66号SPI中断号(共享中断的offset id号)

计算出来的实际中断号即为32 + 66 = 98号中断,  1表示上升沿触发中断。

5.11 子节点:xxx

            i2c@1,0   
                compatible = "acme,a1234-i2c-bus";  
                #address-cells = <1>;  
                #size-cells = <0>;  
                reg = <1 0 0x1000>;  
                interrupts = < 6 2 >;  
                rtc@58   
                    compatible = "maxim,ds1338";  
                    reg = <58>;  
                    interrupts = < 7 3 >;  
                ;  

在上图中,rtc@58是i2c@1,0的子节点。

第6章 设备节点的中断与SOC的中断控制器

以上是关于[架构之路-34]:目标系统 - 系统软件 - Linux OS硬件电路的文本描述:设备树的构成属性解析使用的主要内容,如果未能解决你的问题,请参考以下文章

[架构之路-56]:目标系统 - 平台软件 - 总体架构概述

[架构之路-28]:目标系统 - 系统软件 - Linux OS内核功能架构图解内核构建内核启动流程

[架构之路-25]:目标系统 - 系统软件 - bootloader uboot内存映射与启动流程

[架构之路-21]:目标系统 - 系统软件 - 计算机系统架构计算机指令系统结构化程序与分层编程。

[架构之路-29]:目标系统 - 系统软件 - Linux OS内核以及内核驱动的调试技术

[架构之路-26]:目标系统 - 系统软件 - bootloader uboot使用方法常用命令