Device Tree (一) - dts基本概念和语法

一,ARM设备树起源

在过去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,很多代码只是在描述板级细节,而这些板级细节对于内核来讲,不过是垃圾,如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data。读者若有兴趣,可以统计一下常见的s3c2410、s3c6410等板级目录,代码量在数万行。

设备树是一种描述硬件的数据结构,它起源于OpenFirmware(OF)。在Linux2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx中,采用设备树后,许多硬件的细节可以直接通过它传递给Linux,而不再需要在内核中进行大量的冗余编码。

设备树由一系列被命名的节点(Node)和属性(Property)组成,而节点本身可以包含子节点。所谓属性,其实就是成对出现的名称和值。在设备树中,可描述的信息包括(原先这些信息大多被硬编码在内核中):

  • CPU的数量和类别。

  • 内存基地址和大小。

  • 总线和桥。

  • 外设连接。

  • 中断控制器和中断使用情况。

  • GPIO控制器和GPIO使用情况。

  • 时钟控制器和时钟使用情况。

它基本上就是画一棵电路板上CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核,然后内核可以识别这棵树,并根据它展开出Linux内核中的platform_device、i2c_client、spi_device等设备,而这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。

二,dtc dtb dts等

整个设备树牵涉面比较广,既增加了新的用于描述设备硬件信息的文本格式,又增加了编译这个文本的工具,同时Bootloader也需要支持将编译后的设备树传递给Linux内核。

1,DTS

DTS:文件.dts是一种ASCII文本格式的设备树描述,此文本格式非常人性化,适合人类的阅读习惯。基本上,在ARM Linux中,一个.dts文件对应一个ARM的设备,一般放置在内核的arch/arm/boot/dts/目录中。值得注意的是,在arch/powerpc/boot/dts、arch/powerpc/boot/dts、arch/c6x/boot/dts、arch/openrisc/boot/dts等目录中,也存在大量的.dts文件,这证明DTS绝对不是ARM的专利。

由于一个SoC可能对应多个设备(一个SoC可以对应多个产品和电路板),这些.dts文件势必须包含许多共同的部分,Linux内核为了简化,把SoC公用的部分或者多个设备共同的部分一般提炼为.dtsi,类似于C语言的头文件。其他的设备对应的.dts就包括这个.dtsi。譬如,对于VEXPRESS而言,vexpress-v2m.dtsi就被vexpress-v2p-ca9.dts所引用,vexpress-v2p-ca9.dts有如下一行代码:

/include/ "vexpress-v2m.dtsi"

当然,和C语言的头文件类似,.dtsi也可以包括其他的.dtsi,譬如几乎所有的ARM SoC的.dtsi都引用了skeleton.dtsi。

文件.dts(或者其包括的.dtsi)的基本元素即为前文所述的节点和属性,代码清单1给出了一个设备树结构的模版。

/* 代码清单1设备树结构模版 */
/{
    node1{
        a-string-property = "A string";
        a-string-list-property = "first string", "second string";
        a-byte-data-property = [0x01 0x23 0x34 0x56];
        child-node1 {
            first-child-property;
            second-child-property = <1>;
            a-string-property = "Hello, world";
        };
        child-node2 {
        
        };
    };
    node2{
        an-empty-property;
        a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
        child-node1 {
        
        };
    };
};

上述.dts文件并没有什么真实的用途,但它基本表征了一个设备树源文件的结构:

1个root节点"/";root节点下面含一系列子节点,本例中为node1和node2;节点node1下又含有一系列子节点,本例中为child-node1和child-node2;各节点都有一系列属性。这些属性可能为空,如an-empty-property;可能为字符串,如a-string-property;可能为字符串数组,如a-string-list-property;可能为Cells(由u32整数组成),如second-child-property;可能为二进制数,如a-byte-data-property。

下面以一个最简单的设备为例来看如何写一个.dts文件。如下图所示,假设此设备的配置如下:

1个双核ARM Cortex-A9 32位处理器;ARM本地总线上的内存映射区域分布有两个串口(分别位于0x101F1000和0x101F2000)、GPIO控制器(位于0x101F3000)、SPI控制器(位于0x10170000)、中断控制器(位于0x10140000)和一个外部总线桥;外部总线桥上又连接了SMC SMC91111以太网(位于0x10100000)、I2C控制器(位于0x10160000)、64MB NOR Flash(位于0x30000000);外部总线桥上连接的I2C控制器所对应的I2C总线上又连接了Maxim DS1338实时钟(I2C地址为0x58)。

对于上图所示硬件结构图,如果用".dts"描述,则其对应的".dts"文件如代码清单2所示。

/* 代码清单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>;
        };
    };
};

在上述.dts文件中,可以看出external-bus是根节点的子节点,而I2C又是external-bus的子节点,RTC又进一步是I2C的子节点。每一级节点都有一些属性信息,本章后续部分会进行详细解释。

2,DTC

DTC是将.dts编译为.dtb的工具。DTC的源代码位于内核的scripts/dtc目录中,在Linux内核使能了设备树的情况下,编译内核的时候主机工具DTC会被编译出来,对应于scripts/dtc/Makefile中"hostprogs-y:=dtc"这一hostprogs的编译目标。

当然,DTC也可以在Ubuntu中单独安装,命令如下:

sudo apt-get install device-tree-compiler

在Linux内核的arch/arm/boot/dts/Makefile中,描述了当某种SoC被选中后,哪些.dtb文件会被编译出来,如与VEXPRESS对应的.dtb包括:

dtb-$(CONFIG_ARCH_VEXPRESS) += vexpress-v2p-ca5s.dtb \
    vexpress-v2p-ca9.dtb \
    vexpress-v2p-ca15-tc1.dtb \
    vexpress-v2p-ca15_a7.dtb \
    xenvm-4.2.dtb

在Linux下,我们可以单独编译设备树文件。当我们在Linux内核下运行make dtbs时,若我们之前选择了ARCH_VEXPRESS,上述.dtb都会由对应的.dts编译出来,因为arch/arm/Makefile中含有一个.dtbs编译目标项目。

编译、反编译实例命令如下,"-I"指定输入格式,"-O"指定输出格式,"-o"指定输出文件。

./scripts/dtc/dtc -I dts -O dtb -o tmp.dtb arch/arm/boot/dts/xxx.dts // 编译dts为dtb
./scripts/dtc/dtc -I dtb -O dts -o tmp.dts arch/arm/boot/dts/xxx.dtb//反编译dtb为dts

3,DTB

文件.dtb是.dts被DTC编译后的二进制格式的设备树描述,可由Linux内核解析,当然U-Boot这样的bootloader也是可以识别.dtb的。

通常在我们为电路板制作NAND、SD启动映像时,会为.dtb文件单独留下一个很小的区域以存放之,之后bootloader在引导内核的过程中,会先读取该.dtb到内存。

Linux内核也支持一种变通的模式,可以不把.dtb文件单独存放,而是直接和zImage绑定在一起做成一个映像文件,类似cat zImage xxx.dtb>zImage_with_dtb的效果。当然内核编译时候要使能CONFIG_ARM_APPENDED_DTB这个选项,以支持"Use appended device tree blob to zImage"(见Linux内核中的菜单)。

4,绑定(Binding)

对于设备树中的节点和属性具体是如何来描述设备的硬件细节的,一般需要文档来进行讲解,文档的后缀名一般为.txt。在这个.txt文件中,需要描述对应节点的兼容性、必需的属性和可选的属性。

这些文档位于内核的Documentation/devicetree/bindings目录下,其下又分为很多子目录。譬如,Documentation/devicetree/bindings/i2c/i2c-xiic.txt描述了Xilinx的I2C控制器,其内容如下:

Xilinx IIC controller:
Required properties:
    - compatible : Must be "xlnx,xps-iic-2.00.a"
    - reg : IIC register location and length
    - interrupts : IIC controller unterrupt
    - #address-cells = <1>
    - #size-cells = <0>
Optional properties:
    - Child nodes conforming to i2c bus binding
Example:
    axi_iic_0: i2c@40800000 {
        #address-cells = <1>;
        #size-cells = <0>;
        reg = <0x40800000 0x10000>;
        compatible = "xlnx,xps-iic-2.00.a";
        interrupts = <1 2>;
    };

基本可以看出,设备树绑定文档的主要内容包括:

  • 关于该模块最基本的描述。

  • 必需属性(Required Properties)的描述。

  • 可选属性(Optional Properties)的描述。

  • 一个实例。

Linux内核下的scripts/checkpatch.pl会运行一个检查,如果有人在设备树中新添加了compatible字符串,而没有添加相应的文档进行解释,checkpatch程序会报出警告: UNDOCUMENTED_DT_STRINGDT compatible string xxx appears un-documented,因此程序员要养成及时写DT Binding文档的习惯。

5,Bootloader

Uboot设备从v1.1.3开始支持设备树,其对ARM的支持则是和ARM内核支持设备树同期完成。为了使能设备树,需要在编译Uboot的时候在config文件中加入:

#define CONFIG_OF_LIBFDT

在Uboot中,可以从NAND、SD或者TFTP等任意介质中将.dtb读入内存,假设.dtb放入的内存地址为0x71000000,之后可在Uboot中运行fdt addr命令设置.dtb的地址,如:

UBoot> fdt addr 0x71000000

fdt的其他命令就变得可以使用,如fdt resize、fdt print等。

对于ARM来讲,可以通过bootz kernel_addr initrd_address dtb_address的命令来启动内核,即dtb_address作为bootz或者bootm的最后一次参数,第一个参数为内核映像的地址,第二个参数为initrd的地址,若不存在initrd,可以用"-"符号代替。

6,内核对设备树的处理

从源代码文件dts文件开始,设备树的处理过程为:

1)dts文件在PC上被编译为dtb文件。 

2)uboot把dtb文件传给内核。 

3)内核解析dtb文件,把每一个节点转化为device_node结构体。

4)对于某些device_node结构体,会被转化为platform_device结构体。

三,语法规范

Device Tree不是要描述系统中所有的硬件信息。基本上,那些可以动态探测到的设备是不需要描述的,例如USB device。不过对于SOC上的usb host controller,它是无法动态识别的,需要在device tree中描述。

1,设备节点

在设备树中节点的命名格式如下:

node-name@unit-address
node-name:
是设备节点的名称,为ASCII字符串,节点名字应该能够清晰的描述出节点的功能,比如"uart1"就表示这个节点是UART1外设。
unit-address:
一般表示设备的地址或者寄存器首地址,如果某个节点没有地址或者寄存器的,“unit-address”可以不要。
注:根节点没有node-name或者unit-address,它被定义为/。
Devicetree Example:

2,属性值

dts描述一个键的值有多种方式,当然,一个键也可以没有值。

Value

Description

Example

<empty>

Value is empty. Used for conveying true-false information, when the presence or absence

 of the property itself is sufffciently descriptive.

ranges;

<u32> 

A 32-bit integer in big-endian format. Example: the 32-bit value 0x11223344 would

be represented in memory as:

address 11

address+1 22

address+2 33

address+3 44

#address-cells = <2>;

<u64>

Represents a 64-bit integer in big-endian format. Consists of two <u32> values where

the first value contains the most signiffcant bits of the integer and the second value

contains the least signiffcant bits.

Example: the 64-bit value 0x1122334455667788 would be represented as two cells as:

<0x11223344 0x55667788>.

The value would be represented in memory as:

address 11

address+1 22

address+2 33

address+3 44

address+4 55

address+5 66

address+6 77

address+7 88

reg = <0x11223344 0x55667788>;

<string>

Strings are printable and null-terminated. Example: the string “hello” would be represented

 in memory as:

address 68 'h'

address+1 65 'e'

address+2 6C 'l'

address+3 6C 'l'

address+4 6F 'o'

address+5 00 '\0'

compatible = "arm,cortex-a9-pmu";

<prop-encoded-array>

Format is speciffc to the property. See the property definition.

reg = <0x40ae0000 0x1000 0x400f0000 0x40>;

<phandle>

A <u32> value. A phandle value is a way to reference another node in the devicetree.

Any node that can be referenced defines a phandle property with a unique <u32> value.

That number is used for the value of properties with a phandle value type.

phandle = <1>;

<stringlist>

A list of <string> values concatenated together.

Example: The string list “hello”,”world” would be represented in memory as:

address 68 'h'

address+1 65 'e'

address+2 6C 'l'

address+3 6C 'l'

address+4 6F 'o'

address+5 00 '\0'

address+6 77 'w'

address+7 6f 'o'

address+8 72 'r'

address+9 6C 'l'

address+10 64 'd'

address+11 00 '\0'

compatible = "netgear,readynas-duo-v2", "netgear,readynas",

"marvell,kirkwood-88f6282", "marvell,kirkwood";

字节序列,用中括号包围起来,如:

local-mac-address = [00 00 12 34 56 78]; // 每个byte使用2个16进制数来表示

local-mac-address = [000012345678]; // 每个byte使用2个16进制数来表示

3,设备节点的标准属性

3.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”和“imx-audio-wm8960”表示驱动模块名字。sound这个设备首先使用第一个兼容值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查。

3.2 model属性

model 属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的,比如:

model = "wm8960-audio";
3.3 phandle属性

phandle 属性为devicetree中唯一的节点指定一个数字标识符,节点中的phanle属性,它的取值必须是唯一的(不要跟其它的phandle值一样):

pic@10000000 {

    phandle = <1>;

    interrupt-controller;

};

another-device-node {

    interrupt-parent = <1>; //使用phandle值为1来引用上述节点

};

注:DTS中的大多数设备树将不包含显式的phandle属性,当DTS被编译成二进制DTB格式时,DTC工具会自动插入phandle属性。

3.4 status属性

status属性看名字就知道是和设备状态有关的,status属性值也是字符串,字符串是设备的状态信息,可选的状态如下表所示:

Value

Description

"okay"

Indicates the device is operational.

"disabled"

Indicates that the device is not presently operational, but it might become operational in the future (for

example, something is not plugged in, or switched off).

"reserved"

Indicates that the device is operational, but should not be used. Typically this is used for devices that

are controlled by another software component, such as platform ffrmware.

"fail"

Indicates that the device is not operational. A serious error was detected in the device, and it is unlikely

to become operational without repair.

"fail-sss" 

Indicates that the device is not operational. A serious error was detected in the device and it is unlikely

to become operational without repair. The sss portion of the value is speciffc to the device and indicates

the error condition detected.

3.5 reg 属性

reg属性的一般是(address, length)对,reg属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。

例如:一个设备有两个寄存器块,一个的地址是0x3000,占据32字节;另一个的地址是0xFE00,占据256字节,表示如下:

reg = <0x3000 0x20 0xFE00 0x100>;

注:上述对应#address-cells = <1>; #size-cells = <1>;

3.6 #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 这个数据所占用的字长。

Example1:

/ {
    #address-cells = <0x1>; //在 root node下使用1个u32来代表address。
    #size-cells = <0x0>; // 在root node下使用0个u32来代表size。
    ...
    ...
    memory {        // memory device
        ...
        reg = <0x90000000>;
            // 0x90000000是存取memory的address
        ...
    };
    ...
    ...
}

Example2:

/ {
    #address-cells = <0x1>; //在root node下使用1个u32来代表address。
    #size-cells = <0x1>; //在root node下使用1个u32来代表size。
    ...
    ...
    memory { // memory device
        ...
        reg = <0x90000000 0x800000>;
            // 0x90000000 是存取 memory 的 address
            // 0x800000 是 memory 的 size。
        ...
    };
    ...
    ...
}

Example3:

/ {
    #address-cells = <0x2>; // 在root node下使用2个u32来代表address。
    #size-cells = <0x1>; // 在root node下使用1个u32来代表size。
    ...
    ...
    memory { // memory device
        ...
        reg = <0x90000000 00000000 0x800000>;
            // 0x90000000 00000000 是存取memory的address
            // 0x800000 是memory的size。
        ...
    };
    ...
    ...
}

Example4:

/ {
    #address-cells = <0x2>; // 在root node下使用2个u32来代表address。
    #size-cells = <0x2>; // 在root node下使用2个u32来代表size。
    ...
    ...
    memory { // memory device
        ...
        reg = <0x90000000 00000000 0x800000 00000000>;
            // 0x90000000 00000000 是存取memory的address
            // 0x800000 00000000 是memory的size。
        ...
    };
    ...
    ...
}
3.7 interrupt

1)s3c24xx.dtsi。位于linux-3.14\arch\arm\boot\dts目录下,具体该文件的内容如下(有些内容省略了,领会精神即可,不需要描述每一个硬件定义的细节):

#include "skeleton.dtsi"

/ {
    compatible = "samsung,s3c24xx";
    interrupt-parent = <&intc>;

    aliases {
        pinctrl0 = &pinctrl_0;
    };

    intc:interrupt-controller@4a000000 {
        compatible = "samsung,s3c2410-irq";
        reg = <0x4a000000 0x100>;
        interrupt-controller;
        #interrupt-cells = <4>;
    };

    serial@50000000 {
        compatible = "samsung,s3c2410-uart";
        reg = <0x50000000 0x4000>;
        interrupts = <1 0 4 28>, <1 1 4 28>;
        status = "disabled";
    };

    pinctrl_0: pinctrl@56000000 {
        reg = <0x56000000 0x1000>;

        wakeup-interrupt-controller {
            compatible = "samsung,s3c2410-wakeup-eint";
            interrupts = <0 0 0 3>,
                     <0 0 1 3>,
                     <0 0 2 3>,
                     <0 0 3 3>,
                     <0 0 4 4>,
                     <0 0 5 4>;
        };
    };
……
};

具体各个HW block的interrupt source是如何物理的连接到interrupt controller的呢?在dts文件中是用interrupt-parent这个属性来标识的。且慢,这里定义interrupt-parent属性的是root node,难道root node会产生中断到interrupt controller吗?当然不会,只不过如果一个能够产生中断的device node没有定义interrupt-parent的话,其interrupt-parent属性就是跟随parent node。因此,与其在所有的下游设备中定义interrupt-parent,不如统一在root node中定义了。

关于interrupt,我们值得进一步描述。在Device Tree中,有一个概念叫做interrupt tree,也就是说interrupt也是一个树状结构。我们以下图为例(该图来自Power_ePAPR_APPROVED_v1.1):

系统中有一个interrrupt tree的根节点,device1、device2以及PCI host bridge的interrupt line都是连接到root interrupt controller的。PCI host bridge设备中有一些下游的设备,也会产生中断,但是他们的中断都是连接到PCI host bridge上的interrupt controller(术语叫做interrupt nexus),然后报告到root interrupt controller的。每个能产生中断的设备都可以产生一个或者多个interrupt,每个interrupt source(另外一个术语叫做interrupt specifier,描述了interrupt source的信息)都是限定在其所属的interrupt domain中。

在了解了上述的概念后,我们可以回头再看看interrupt-parent这个属性。其实这个属性是建立interrupt tree的关键属性。它指明了设备树中的各个device node如何路由interrupt event。另外,需要提醒的是interrupt controller也是可以级联的,上图中没有表示出来。那么在这种情况下如何定义interrupt tree的root呢?那个没有定义interrupt-parent的interrupt controller就是root。

intc(node name是interrupt-controller@4a000000 ,我这里直接使用lable)是描述interrupt controller的device node。根据S3C24xx的datasheet,我们知道interrupt controller的寄存器地址从0x4a000000开始,长度为0x100(实际2451的interrupt的寄存器地址空间没有那么长,0x4a000074是最后一个寄存器),也就是reg属性定义的内容。interrupt-controller属性为空,只是用来标识该node是一个interrupt controller而不是interrupt nexus(interrupt nexus需要在不同的interrupt domains之间进行翻译,需要定义interrupt-map的属性,本文不涉及这部分的内容)。#interrupt-cells 和#address-cells概念是类似的,也就是说,用多少个u32来标识一个interrupt source。我们可以看到,在具体HW block的interrupt定义中都是用了4个u32来表示,例如串口的中断是这样定义的:

interrupts = <1 0 4 28>, <1 1 4 28>;

2)与中断相关的属性总结

设备树中还可以包含中断连接信息,对于中断控制器而言,它提供如下属性:

interrupt-controller - 这个属性为空,中断控制器应该加上此属性表明自己的身份。

interrupt-cells - 与#address-cell和#size-cells相似,它表明连接此中断控制器的设备的中断属性的cell大小。

在整个设备树中,与中断相关的属性还包括:

interrupt-parent - 设备节点通过它来指定它所依附的中断控制器的phandle,当节点没有指定interrupt-parent时,则从父级节点继承。

interrupts - 用到了中断的设备节点,通过它指定中断号、触发方法等,这个属性具体含有多少个cell,由它依附的中断控制器节点的#interrupt-cells属性决定。

而每个cell具体又是什么含义,一般由驱动的实现决定,而且也会在设备树的绑定文档中说明。一般,如果父节点的该属性的值是3,则子节点的interrupts一个cell的三个32bits整数值分别为:<中断域 中断 触发方式>,如果父节点的该属性是2,则是<中断 触发方式>。

譬如,对于ARM GIC中断控制器而言,#interrupt-cells为3,3个cell的具体含义在Docmentation/devicetree/bingdings/arm/gic.txt中就有如下文字说明:

The 1st cell is the interrupt type;0 for SPI interrupts,1 for PPI interrupts.

The 2ed cell contains the interrupt number for the interrupt type.

SPI interrupts are in range (0-987). PPI interrupts are in the range [0-15].

The 3rd cell in the flags, encoded as follows:

bits[3:0] trigger type and level flags.

    1 = low-to-high edge triggered

    2 = high-to-low edge triggered

    4 = active high level-sensitive

    8 = active low level-sensitive

bits[15:8] PPI interrupt cpu mask. Each bit corresponds to each of

the 8 possible cpus attached to the GIC. A bit set to '1' indicated

the interrupt is wired to that cpu. Only valid for PPI interrupts.

3.8 gpio属性

"gpio-controller",用来说明该节点描述的是一个gpio控制器

"#gpio-cells",用来描述gpio使用节点的属性一个cell的内容,即 属性 = <&引用GPIO节点别名 GPIO标号 工作模式>

3.9 ranges属性

我们已经讨论了如何为设备分配地址,但此时这些地址只是设备节点的本地地址。它尚未描述如

何从这些地址映射到CPU可以使用的地址。

根节点始终描述CPU的地址空间视图。根节点的子节点已经在使用CPU的地址域,因此不需要任

何显式映射。例如,串行@ 101f0000设备直接分配地址0x101f0000。

不是根的直接子节点的节点不使用CPU的地址域。为了获得内存映射地址,设备树必须指定如何

将地址从一个域转换为另一个域。该ranges property用于此目的。

以下是添加了ranges property的示例设备树:

/ DTS-V1 /;
/ {
    compatible =“acme,coyotes-revenge”;
    #address-cells = <1>;
    #size-cells = <1>;
    ...
    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>;
    };
    i2c @ 1,0 {
        compatible =“acme,a1234-i2c-bus”;
        #size-cells = <0>;
        reg = <1 0 0x1000>;
            rtc @ 58 {
                compatible =“maxim,ds1338”;
                reg = <58>;
        };
    };
    flash @ 2,0 {
        compatible =“samsung,k8f1315ebm”,“cfi-flash”;
        reg = <2 0 0x4000000>;
        };
    };
};

ranges是一个地址翻译列表。范围表中的每个条目都是一个元组,其中包含子地址,父地址和

子地址空间中区域的大小。他的成员组成如下:

ranges = <local_address parent_address address_size>

local_address:这里local_address就是我们设备树中external-bus节点的#address-cells = <2>;

parent_address:这里就是我们根节点的#address-cells = <1>;

address_size:就是#size-cells = <1>;

对于我们示例中的外部总线,子地址是2个单

元,父地址是1个单元,大小也是1个单元。ranges范围映射如下:

  • 芯片片选为0的偏移为0,其映射到地址范围0x10100000…0x1010ffff

  • 芯片片选为1的偏移为0,其映射到地址范围0x10160000…0x1016ffff

  • 芯片片选为2的偏移为0,其映射到地址范围0x30000000…0x30ffffff

或者,如果父地址空间和子地址空间相同,则节点可以改为添加空 ranges 属性。空范围属性的

存在意味着子地址空间中的地址以1:1映射到父地址空间。

3.10 name属性

name属性值为字符串,name属性用于记录节点的名字,name属性已经被弃用,不推荐使用name属性,一些老的设备树文件可能会使用此属性。

3.11 device_type属性

device_type属性值为字符串,IEEE 1275会用到此属性,用于描述设备的FCode,但是设备树没有FCode,所以此属性也被抛弃了。此属性只能用于

cpu节点或者memory节点。

memory@30000000 {
    device_type = "memory";
    reg = <0x30000000 0x4000000>;
};

4,根节点

每个设备树文件只有一个根节点,其他所有的设备节点都是它的子节点,它的路径是/。根节点有以下属性:

5,特殊节点

5.1 /aliases 子节点

aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。

Example:

aliases { 
    serial0 = "/simple-bus@fe000000/serial@llc500";
    ethernet0 = "/simple-bus@fe000000/ethernet@31c000"; 
};
5.2 /memory 子节点

所有设备树都需要一个memory设备节点,它描述了系统的物理内存布局。如果系统有多个内存块,可以创建多个memory节点,或者可以在单个memory节点的reg属性中指定这些地址范围和内存空间大小。

例如一个64bit的设备有如下的物理内存布局:

  • RAM: starting address 0x0, length 0x80000000 (2 GB) 

  • RAM: starting address 0x100000000, length 0x100000000 (4 GB)

  • #address-cells = <2> and #size-cells = <2>

Example #1

memory@0 { 
    device_type = "memory"; 
    reg = <0x000000000 0x00000000 0x00000000 0x80000000 
    0x000000001 0x00000000 0x00000001 0x00000000>; 
};

Example #2

memory@0 { 
    device_type = "memory"; 
    reg = <0x000000000 0x00000000 0x00000000 0x80000000>; 
};
memory@100000000 { 
    device_type = "memory"; 
    reg = <0x000000001 0x00000000 0x00000001 0x00000000>; 
};
5.3 /chosen 子节点

chosen 并不是一个真实的设备, chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。

5.4 /cpus 子节点

cpus节点下有1个或多个cpu子节点, cpu子节点中用reg属性用来标明自己是哪一个cpu。

6,引用其它节点

相同的节点的不同属性信息都会被合并,相同节点的相同的属性会被重写。

6.1 phandle

节点中的phandle值必须是唯一的。

6.2 label

[label:] node-name[@unit-address] {

   [properties definitions]

   [child nodes]

}

label方便在dts文件中引用。

在dts文件中中,可以使用类似c语言的Labels and References机制。定义一个lable,唯一标识一个node或者property,

后续可以使用&来引用这个lable。DTC会将lable转换成u32的整数值放入到DTB中,用户层面就不再关心具体转换的整数值了。

7,一个设备树的全景视图

参考链接:

https://blog.51cto.com/u_15169172/4065013

Device Tree(二):基本概念

<<Linux设备驱动开发详解>>

  • 23
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值