1 Linux设备树概述1.1 Linux设备树概述
    Linux设备树是一个包含节点和属性的简单树状结构。属性是基于key-value对的,而节点可以同时包含属性和子节点。下面这个树就是一个典型结构
/ {
     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 >; /* each number (cell) is a uint32 */
                 child-node1 {
                                      };
                };
};
这棵树没有描述任何东西,但是它体现了节点的一些属性:
l 一个单独的根节点:“/”
l 两个子节点:“node1”“node2”
l 两个 node1 的子节点:“child-node1”“child-node2”
l 一堆分散在树里的属性
可以这么简单理解:节点就是树枝,属性就是树叶;树枝上可以有再长树枝也可以长树叶,而树叶上则不会再长树枝。
属性是基于key-value结构的,value可以为空或者特定格式的字符串内容。由于数据类型并不被编码到最终的数据结构中,设备树源代码中仅能支持有限的几种基本数据类型,如下:
l 文本字符串(无结束符)可以用双引号表示:
string-property = "a string"
l 'Cells'32 位无符号整数,用尖括号表示:
cell-property = <0xbeef xabcd>
l 二进制数据用方括号表示
binary-property = [0x01 0x23 0x45 0x67];
l 不同表示形式的数据可以使用逗号连在一起
mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
l 逗号也可用于创建字符串列表
string-list = "red fish", "blue fish";
1.2 基本概念1.2.1 初始结构
第一步就是构建一个基本结构,这是一个设备树最基本的结构。在这个阶段,需要一个唯一的标识该机器。
/ {
compatible = "gpio-leds";
};
compatible指定了系统的名称。它包含了一个“<>,<>”形式的字符串。重要的是要指定一个确切的设备,并且包括制造商的名字,以避免命名空间冲突。由于操作系统会使用 compatible的值来决定如何在机器上运行,所以正确的设置这个属性十分重要。
1.2.2 中央处理器
    第二步就是描述CPU。先添加一个名为“cpus”的容器节点,然后为每个CPU分别添加子节点TI AM437x平台为例
/ {
compatible = "ti,am437x-gp-evm";
cpus {
cpu@0 {
compatible = "ti,am4372";
};
cpu@1 {
compatible = "ti,am4372";
};
};
};
每个cpu节点compatible属性是一个“<>,<>”形式的字符串,并指定了确切的cpu,就像顶层的compatible属性一样。
1.2.3 节点名称
    每个节点必须有一个“<>[@<>]”形式的名字。
l << span>名称>就是一个不超过31位的简单ascii字符串。通常,节点的命名应该根据它所体现的是什么样的设备。比如一个3com以太网适配器的节点就应该命名为 ethernet,而不应该是3com509
l 如果该节点描述的设备有一个地址的话,还应该加上设备地址(unit-address)。通常,设备地址就是用来访问该设备的主地址,并且该地址也在节点的reg属性中列出
l 同级节点命名必须是唯一的,但只要地址不同,多个节点也可以使用一样的通用名例如serial@101f1000serial@101f2000
1.2.4 设备
系统中每个设备都表示为一个设备树节点。所以接下来就应该为这个设备树填充设备节点。
/ {
compatible = "ti,am437x-gp-evm";

cpus {
cpu@0 {
compatible = "ti,am4372";
};
cpu@1{
compatible = "ti,am4372";
};
};

serial@101F0000 {
compatible = "ti,omap2-uart";
};

serial@101F2000 {
compatible = "ti,am4372-uart";
};
gpio@101F3000 {
compatible = "ti,pl061";
};

spi@10115000 {
compatible = "ti,ads7846";
};

external-bus {
ethernet@0,0 {
compatible = "ti,smc91c111";
};

i2c@1,0 {
compatible = "ti,tps65218";
rtc@58 {
compatible = "ti,ds1338";
};
};

flash@2,0 {
compatible = "ti,omap2-nand", "omap3-nand";
}
在此树中,已经为系统中的每个设备添加了节点,而且这个层次结构也反映了设备与系统的连接方式。例如,外部总线上的设备就是外部总线节点的子节点,i2c设备就是i2c 总线节点的子节点。通常,这个层次结构表现的是CPU视角的系统视图。
在这颗树中,应该注意这些事情:
l 每个设备节点都拥有一个compatible 属性
l flash闪存节点的compatible属性由两个字符串构成
l 正如前面所述,节点的命名应当反映设备的类型而不是特定的型号。
1.2.5 理解compatible属性
设备树中每个节点都需要一个compatible属性。compatible属性是操作系统用来决定使用哪个设备驱动来绑定到一个设备上的关键因素
compatible是一个字符串列表,其中第一个字符串指定了这个节点所表示的确切的设备,该字符串的格式为:"<>,<>"。剩下的字符串的则表示其它与之相兼容的设备
1.3 如何编址
    可编址设备使用以下属性将地址信息编码进设备树:
reg
#address-cells
#size-cells
    每个可编址设备都有一个元组列表的reg,元组的形式为:reg = <>。每个元组都表示一个该设备使用的地址范围。每个地址值是一个或多个32位整型数列表,称为cell。同样,长度值也可以是一个 cell 列表或者为空。
    由于地址和长度字段都是可变大小的变量,那么父节点的#address-cells#size-cells 属性就用来声明各个字段的cell的数量。换句话说,正确解释一个reg属性需要用到父节点的#address-cells#size-cells的值。
1.3.1 CPU 编址
    CPU节点是一个关于编址的最简单的例子。每个CPU都分配了一个唯一的ID,并且没有CPU id相关的大小信息。
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "ti,am4372";
reg = <0>;
};
cpu@1 {
compatible = "ti,am4372";
reg = <1>;
};
};
    cpu节点中,#address-cells设置为1#size-cells设置为0。这意味着子节点的reg值是一个单一的uint32,这是一个不包含大小字段的地址,为这两个cpu分配的地址是01cpu节点的#size-cells0是因为只为每个cpu分配一个单独的地址。
注意:reg的值和节点名字是相同的。按照惯例,如果一个节点有reg属性,那么该节点的名字就必须包含设备地址,这个设备地址就是reg属性里第一个地址值。
1.3.2 内存映射设备
    cpu节点里单一地址值不同,应该分配给内存映射设备一个地址范围。#size-cells声明每个子节点的reg元组中长度字段的大小。在接下来的例子中,每个地址值是1cell32位),每个长度值也是1cell,这是典型的32位系统。64位的机器则可以使用值为2#address-cells#size-cells来获得在设备树中的64位编址。
/ {
#address-cells = <1>;
#size-cells = <1>;
...

        serial@101f0000 {compatible = "ti,omap2-uart";
reg = <0x101f0000 x >;
};
serial@101f2000 {
compatible = "ti,am4372-uart";
reg = <0x101f2000 x >;
};
gpio@101f3000 {
compatible = "ti,pl061";
reg = <0x101f3000 xspan>
0x101f4000 0x0010>;
};
spi@10115000 {
compatible = "ti,ads7846";
reg = <0x10115000 x >;
};
...
};
    每个设备都被分配了一个基址以及该区域的大小。这个例子中为GPIO分配了两个地址范围:0x101f3000...0x101f3fff0x101f4000..0x101f400f
   一些挂在总线上的设备有不同的编址方案。例如一个带独立片选线的设备也可以连接至外部总线。由于父节点会为其子节点定义地址域,所以可以选择不同的地址映射来最恰当的描述该系统。下面的代码展示了设备连接至外部总线并将其片选号编码进地址的地址分配。
external-bus {
#address-cells = <2>
#size-cells = <1>;
ethernet@0,0 {
compatible = "ti,smc91c111";
reg = <0 x>;
};
i2c@1,0 {
compatible = "ti,a1234-i2c-bus";
reg = <1 x>;
rtc@58 {
compatible = "ti,ds1338";
};
};
flash@2,0 {
compatible = "ti,omap2-nand", "omap3-nand";
reg = <2 x>;
};
};
    外部总线的地址值使用了两个cell,一个用于片选号另一个则用于片选基址的偏移量。而长度字段则还是单个cell,这是因为只有地址的偏移部分才需要一个范围量。所以,在这个例子中,每个reg项都有三个cell:片选号、偏移量和长度。
    由于地址域是包含于一个节点及其子节点的,所以父节点可以自由的定义任何对于该总线来说有意义的编址方案。那些在直接父节点和子节点以外的节点通常不关心本地地址域,而地址应该从一个域映射到另一个域。
1.3.3 非内存映射设备
    其他的设备没有被映射到处理机总线上。虽然这些设备可以有一个地址范围,但他们并不是由CPU直接访问。取而代之的是,父设备的驱动程序会代表CPU执行简介访问。
    i2c设备为例,每个设备都分配了一个地址,但并没有与之关联的长度或范围信息。这看起来和 CPU 的地址分配很像。
       i2c@1,0 {
            compatible = "ti,tps65218";
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <1 x>;
            rtc@58 {
                compatible = "ti,ds1338";
                reg = <58>;
            };
        };

1.3.4 范围(地址转换)
    我们已经讨论了如何给设备分配地址,但目前来说这些地址还只是设备节点的本地地址,我们还没有描述如何将这些地址映射成CPU可使用的地址。
    根节点始终描述的是CPU视角的地址空间。根节点的子节点已经使用的是CPU的地址域,所以它们不需要任何直接映射。例如,serial@101f0000 设备就是直接分配的 0x101f0000 地址。
    那些非根节点直接子节点的节点就没有使用CPU地址域。为了得到一个内存映射地址,设备树必须指定从一个域到另一个域地址转换地方法,而ranges属性就起作用了下面就是一个添加了ranges属性的示例设备树。
/ {
    compatible = "ti,am4372";
    #address-cells = <1>;
   #size-cells = <1>;
    ...
    external-bus {
        #address-cells = <2>
        #size-cells = <1>;
        ranges = <0 x x Chipselect Ethernetspan>
                  1 0  0x10160000   0x10000     
                  2 0  0x30000000   0x10000000>;
        ethernet@0,0 {
            compatible = "ti,smc91c111";
            reg = <0 x>;
        };
        i2c@1,0 {
            compatible = "ti,tps65218";
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <1 x>;
            rtc@58 {
                compatible = "ti,ds1338";
                reg = <58>;
            };
        };
       flash@2,0 {
            compatible = "ti,omap2-nand", "omap2-nand";
            reg = <2 x>;
        };
    };
};

    ranges是一个地址转换列表。ranges表中的每一项都是一个包含子地址、父地址和在子地址空间中区域大小的元组。每个字段的值都取决于子节点的#address-cells、父节点的#address-cells和子节点的#size-cells。以本例中的外部总线来说,子地址是2cell、父地址是1cell、区域大小也是1cell。那么三个ranges被翻译为:
l 从片选0开始的偏移量0被映射为地址范围:0x10100000..0x1010ffff
l 从片选0开始的偏移量1被映射为地址范围:0x10160000..0x1016ffff
l 从片选0开始的偏移量2被映射为地址范围:0x30000000..0x10000000
另外,如果父地址空间和子地址空间是相同的,那么该节点可以添加一个空的range 属性。一个空的range属性意味着子地址将被1:1映射到父地址空间。
    你有可能会问当全都可以设计成1:1映射的时候为何还要使用地址转换。答案就是,有一些具有完全不同地址空间的总线(比如PCI),而它们的细节需要暴露给操作系统。另外一些带有DMA引擎的设备需要知道总线上的真实地址。有时有需要将设备组合到一块,因为他们共享相同的软件可编程物理地址映射。是否应该使用1:1映射在很大程度上取决于来自操作系统的信息以及硬件设计。
    你还应该注意到在i2c@1,0节点中并没有range属性。不同于外部总线,这里的原因是i2c总线上的设备并没有被内存映射到CPU的地址域。相反CPU将通过i2c@1,0设备间接访问rtc@58设备。缺少ranges属性意味着这个设备将不能被出他的父设备之外的任何设备直接访问。
1.4 中断如何工作
    与遵循树的自然结构而进行的地址转换不同,机器上的任何设备都可以发起和终止中断信号。另外地址的编址也不同于中断信号,前者是设备树的自然表示,而后者者表现为独立于设备树结构的节点之间的链接。描述中断连接需要四个属性:
l interrupt-controller一个空的属性定义该节点作为一个接收中断信号的设备。
l #interrupt-cells这是一个中断控制器节点的属性。它声明了该中断控制器的中断指示符中cell的个数(类似于#address-cells#size-cells)。
l interrupt-paren这是一个设备节点的属性,包含一个指向该设备连接的中断控制器的phandle。那些没有interrupt-parent的节点则从它们的父节点中继承该属性。
l Interrupts一个设备节点属性,包含一个中断指示符的列表,对应于该设备上的每个中断输出信号。
    中断指示符是一个或多个cell的数据(由#interrupt-cells指定),这些数据指定了该设备连接至哪些输入中断。在以下的例子中,大部分设备都只有一个输出中断,但也有可能在一个设备上有多个输出中断。一个中断指示符的意义完全取决于与中断控制器设备的binding。每个中断控制器可以决定使用几个cell来唯一的定义一个输入中断。下面的代码添加了中断连接:
/ {
    compatible = "ti,am437x-gp-evm";
    #address-cells = <1>;
    #size-cells = <1>;
    interrupt-parent = < intc>;
    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "ti,am4372";
            reg = <0>;
        };
        cpu@1 {
            compatible = "ti,am4372";
            reg = <1>;
        };
    };
    serial@101f0000 {
        compatible = "ti,am4372-uart";
        reg = <0x101f0000 x >;
        interrupts = < 1 >;
    };

    serial@101f2000 {
        compatible = "ti,am4372-uart";
        reg = <0x101f2000 x >;
        interrupts = < 2 >;
    };

    gpio@101f3000 {
        compatible = "ti,pl061";
        reg = <0x101f3000 xspan>
               0x101f4000 0x0010>;
        interrupts = < 3 >;
    };

    intc: interrupt-controller@10140000 {
        compatible = "ti,pl190";
        reg = <0x10140000 x >;
        interrupt-controller;
        #interrupt-cells = <2>;
    };

    spi@10115000 {
        compatible = "ti,ads7846";
        reg = <0x10115000 x >;
        interrupts = < 4 >;
    };
    external-bus {
        #address-cells = <2>
        #size-cells = <1>;
        ranges = <0 x x Chipselect Ethernetspan>
                  1 0  0x10160000   0x10000     
                  2 0  0x30000000   0x1000000>;
        ethernet@0,0 {
            compatible = "ti,smc91c111";
            reg = <0 x>;
            interrupts = < 5 >;
        };

        i2c@1,0 {
            compatible = "ti,tps65218";
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <1 x>;
            interrupts = < 6 >;
            rtc@58 {
                compatible = "ti,ds1338";
                reg = <58>;
                interrupts = < 7 >;
            };
        };

        flash@2,0 {
            compatible = "ti,omap2-nand", "omap2-nand";
            reg = <2 x>;
        };
    };
};
需要注意的事情这个机器只有一个中断控制器:interrupt-controller@10140000
   中断控制器节点上添加了‘inc:’标签,该标签用于给根节点的interrupt-parent属性分配一个phandle。这个interrupt-parent将成为本系统的默认值,因为所有的子节点都将继承它,除非显示覆写这个属性。
    每个设备使用interrupts属性来不同的中断输入线。
#interrupt-cells2,所以每个中断指示符都有2cell。本例使用一种通用的模式,也就是用第一个cell来编码中断线号;然后用第二个cell编码标志位,比如高电平/低电平有效,或者边缘/水平触发。对于任何给定的中断控制器,请参考该控制器的binding 文档以了解指示符如何编码。
1.5 特殊节点
aliases节点
引用一个特定的节点,通常使用全路径,如/external-bus/ethernet@0,0,但当用户想知道的只是那个设备是eth0”时,这样的全路径就变得很冗长。这时,aliases节点就可以用于指定一个设备全路径的别名。例如:
    aliases {  
        display0 = &lcd0;
    };
操作系统更倾向于使用别名,例如给一个设备分配一个识别符。
   这里有一个新语法display0 = &lcd0;将作为字符串属性并通过引用标签来指定一个节点的全路径。这和之前的phandle = < label >; 形式不同,这是把一个phandle 值插入进一个cell
1.6 备注
参考链接:http://elinux.org/Device_Tree_Usage