Device Tree
的用法
基本数据格式
Device tree是一种简单的节点和属性的树形结构。属性是键值对,而节点可能包括属性和子节点。例如,下面是.dts格式的树形结构:
/ {
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 {
};
};
};
此树形结构很明显的是毫无用处,因为它没描述任何东西,但它确实展示了节点属性的结构。主要有:
1 一个单独的root node:“/”
2 一对子节点:“node1”和“node2”
3 节点1的一对子节点:“child-node1”和”child-node2”
4 分散于树形结构当中的一些属性
属性是简单的键值对,此处的值可以为空,也可以包括任意的字节流。当数据类型没有被编进数据结构时,会有一些基础数据表示法能够在device tree源文件中进行表达。
5 文本串可以用双引号表示
a string-property = "a string"
6 单元格是由尖括号分隔的32 bit无符号整数
a cell-property = <0xbeef 123 0xabcd1234>
7 二进制数据使用的是方
a binary-property = [0x01 0x23 0x45 0x67];
8 不同示意的数据可以用逗号串联在一起
a mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
9 逗号也用来创建字符串列表
a string-list = "red fish", "blue fish";
基本概念
要了解如何使用device tree,我们先从样机和创建一个device tree开始。
样机
以下为虚拟机,由”Acme”制造,名为“Coyote’s Revenge”。
1 一个32bit ARM CPU
2 附属于内存映射串行端口的处理器本地总线,spi总线控制器,i2c控制器,中断控制器和外总线桥
3 256MB的SDRAM
4 基于0X101F1000和0X101F2000的2串行端口
5 基于0x101F3000的GPIO控制器
6 基于0X1017000的拥有以下设备的SPI控制器
a 附属于GPIO #1的有SS pin的MMC slot
7 拥有以下设备的外部总线桥
a 附属于基于0x101100000外总线的SMC91111以太网设备
b 拥有以下设备的基于0x10160000的i2c控制器
c Maxim DS1338 real time clock。响应slave address 1101000(0x58)
8 基于0x30000000的64MB的NOR flash
初始结构
第一步是为设备创建一个骨架结构。这是一具有效的device tree所需的最基本的结构。现在你想唯一的标识此设备。
/ {
compatible = "acme,coyotes-revenge";
};
C
ompatible指定了系统的名称。它包括字符串<manufacturer>,<model>。指定确切的设备是很重要的一点,并且包含制造商以避免命名空间冲突。操作系统将使用compatible值来决定如何在设备上运行,那么将正确数据加入属性中就显得非常重要。
理论上,compatible是OS所需的唯一指定设备的所有数据。如果所有设备资料都是硬编码,那么OS可以在高级compatible属性查找“acme,coyotes-revenge”。
高级中断映射
现在我们开始最有趣的部分,PCI中断映射。一个PCI设备能够使用#NTA,#NTB,#NTC和#NTD来触发中断。如果我们没有多功能PCI设备,就会有某个设备负责使用#NTA来进行中断。然而每个PCI插口或设备都会被连线到中断控制器的不同输入。所以device tree需要一种方式将每个PCI中断信号映射到中断控制器的输入。#interrupt-cells,interrupt-map和interrupt-map-mask属性用来描述中断映射。
事实上,这里描述的中断映射并不局限于PCI总线,任何节点都可以指定复杂的中断映射,但PCI是最常见的情况。
pci@0x10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
bus-ranges = <0 0>;
#address-cells = <3>
#size-cells = <2>;
ranges = <0x42000000 0 0x80000000 0x80000000 0 0x20000000
0x02000000 0 0xa0000000 0xa0000000 0 0x10000000
0x01000000 0 0x00000000 0xb0000000 0 0x01000000>;
#interrupt-cells = <1>;
interrupt-map-mask = <0xf800 0 0 7>;
interrupt-map = <0xc000 0 0 1 &intc 9 3 // 1st slot
0xc000 0 0 2 &intc 10 3
0xc000 0 0 3 &intc 11 3
0xc000 0 0 4 &intc 12 3
0xc800 0 0 1 &intc 10 3 // 2nd slot
0xc800 0 0 2 &intc 11 3
0xc800 0 0 3 &intc 12 3
0xc800 0 0 4 &intc 9 3>;
};
首先你将注意到PCI中断编号只使用一个cell,与使用两个cell的系统中断控制器不同,一个是irq编号,另一个是为了标记。
在示例中,我们有两个4中断线的PCI插槽,所以我们需要将8个中断线映射到中断控制器。这是通过使用interrupt-map属性实现的。
因为中断编号不足以区分位于单独PCI总线的一些PCI设备,我们也需要指出是哪个PCI设备触发中断线。幸运的是我们可以使用每个PCI设备都有一个唯一设备编号。想要区分一些PCI设备的中断,我们需要一个元组,包括PCI设备编号和PCI中断编号。简单来了说,我们要构建一个拥有4 cell的单元中断说明符:
三个#address-cells,包括phys.hi,phys.mid,phys.low,和
一个#interrupt-cell(#INTA,#INTB,#INTC,#INTD)
因为我们只需要PCI地址的设备编号部分,此时可以使用interrupt-map-mask属性。Interrupt-map-mask也是4-元组,像单元中断说明符一样。在示例中可以看到我们只需要phys.hi设备编号部分,并且我们需要3 bit来区分四个中断线。
现在我们可以创建interrupt-map属性。此属性是一个表格,每个在此表格的输入包括子单元中断说明符,一个母handle和一个母单元中断说明符。所以在第一个可以读出PCI中断#INTA被映射到IRQ9。
单元中断说明符最重要的部分是源于phys.hi bit字段的设备编码。设备编码取决于每个PCI主机控制器是如何启用每个设备上的IDSEL pin的。在此示例中,PCI插槽被分配了设备id 24 (0x18),并且PCI插槽2被分配到了设备id 25 (0x19)。每个插槽的phys.hi值是由设备编码移位来了决定的,如下:
为插槽1的phys.hi是0XC000
为插槽2的phys.hi是0Xc800
将这些放在一起,interrupt-map属性显示:
为插槽1的#INTA是IRQ9,主中断控制器的低敏感
为插槽1的#INTB是IRQ10,主中断控制器的低敏感
为插槽1的#INTC是IRQ11,主中断控制器的低敏感
为插槽1的#INTD是IRQ12,主中断控制器的低敏感
并且
为插槽2的#INTA是IRQ10,主中断控制器的低敏感
为插槽2的#INTB是IRQ11,主中断控制器的低敏感
为插槽2的#INTC是IRQ12,主中断控制器的低敏感
为插槽2的#INTD是IRQ9,主中断控制器的低敏感
最后需要注意的是,与interrupt-parent属性相似,一个节点的interrupt-map属性会为所有子节点改变默认中断控制器。在此PCI示例中,这意味着PCI主机桥会成为默认中断控制器。如果通过PCI总线附加上一个设备,而此设备与另外一个中断控制器直接相连的话,它仍然需要指定自己的interrupt-parent属性。
现在我们开始最有趣的部分,PCI中断映射。一个PCI设备能够使用#NTA,#NTB,#NTC和#NTD来触发中断。如果我们没有多功能PCI设备,就会有某个设备负责使用#NTA来进行中断。然而每个PCI插口或设备都会被连线到中断控制器的不同输入。所以device tree需要一种方式将每个PCI中断信号映射到中断控制器的输入。#interrupt-cells,interrupt-map和interrupt-map-mask属性用来描述中断映射。
事实上,这里描述的中断映射并不局限于PCI总线,任何节点都可以指定复杂的中断映射,但PCI是最常见的情况。
pci@0x10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
bus-ranges = <0 0>;
#address-cells = <3>
#size-cells = <2>;
ranges = <0x42000000 0 0x80000000 0x80000000 0 0x20000000
0x02000000 0 0xa0000000 0xa0000000 0 0x10000000
0x01000000 0 0x00000000 0xb0000000 0 0x01000000>;
#interrupt-cells = <1>;
interrupt-map-mask = <0xf800 0 0 7>;
interrupt-map = <0xc000 0 0 1 &intc 9 3 // 1st slot
0xc000 0 0 2 &intc 10 3
0xc000 0 0 3 &intc 11 3
0xc000 0 0 4 &intc 12 3
0xc800 0 0 1 &intc 10 3 // 2nd slot
0xc800 0 0 2 &intc 11 3
0xc800 0 0 3 &intc 12 3
0xc800 0 0 4 &intc 9 3>;
};
首先你将注意到PCI中断编号只使用一个cell,与使用两个cell的系统中断控制器不同,一个是irq编号,另一个是为了标记。
在示例中,我们有两个4中断线的PCI插槽,所以我们需要将8个中断线映射到中断控制器。这是通过使用interrupt-map属性实现的。
因为中断编号不足以区分位于单独PCI总线的一些PCI设备,我们也需要指出是哪个PCI设备触发中断线。幸运的是我们可以使用每个PCI设备都有一个唯一设备编号。想要区分一些PCI设备的中断,我们需要一个元组,包括PCI设备编号和PCI中断编号。简单来了说,我们要构建一个拥有4 cell的单元中断说明符:
三个#address-cells,包括phys.hi,phys.mid,phys.low,和
一个#interrupt-cell(#INTA,#INTB,#INTC,#INTD)
因为我们只需要PCI地址的设备编号部分,此时可以使用interrupt-map-mask属性。Interrupt-map-mask也是4-元组,像单元中断说明符一样。在示例中可以看到我们只需要phys.hi设备编号部分,并且我们需要3 bit来区分四个中断线。
现在我们可以创建interrupt-map属性。此属性是一个表格,每个在此表格的输入包括子单元中断说明符,一个母handle和一个母单元中断说明符。所以在第一个可以读出PCI中断#INTA被映射到IRQ9。
单元中断说明符最重要的部分是源于phys.hi bit字段的设备编码。设备编码取决于每个PCI主机控制器是如何启用每个设备上的IDSEL pin的。在此示例中,PCI插槽被分配了设备id 24 (0x18),并且PCI插槽2被分配到了设备id 25 (0x19)。每个插槽的phys.hi值是由设备编码移位来了决定的,如下:
为插槽1的phys.hi是0XC000
为插槽2的phys.hi是0Xc800
将这些放在一起,interrupt-map属性显示:
为插槽1的#INTA是IRQ9,主中断控制器的低敏感
为插槽1的#INTB是IRQ10,主中断控制器的低敏感
为插槽1的#INTC是IRQ11,主中断控制器的低敏感
为插槽1的#INTD是IRQ12,主中断控制器的低敏感
并且
为插槽2的#INTA是IRQ10,主中断控制器的低敏感
为插槽2的#INTB是IRQ11,主中断控制器的低敏感
为插槽2的#INTC是IRQ12,主中断控制器的低敏感
为插槽2的#INTD是IRQ9,主中断控制器的低敏感
最后需要注意的是,与interrupt-parent属性相似,一个节点的interrupt-map属性会为所有子节点改变默认中断控制器。在此PCI示例中,这意味着PCI主机桥会成为默认中断控制器。如果通过PCI总线附加上一个设备,而此设备与另外一个中断控制器直接相连的话,它仍然需要指定自己的interrupt-parent属性。
CPU
下一步是描述CPU。一个名为“cpus”的container node为每个CPU添加了一个子节点。在此情况下此系统为源于ARM的双核Cortex A9系统。
/ {
compatible = "acme,coyotes-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
};
cpu@1 {
compatible = "arm,cortex-a9";
};
};
};
在每个cpu节点的兼容属性是字符串<manufacturer>,<model>,用以指定确切的cpu,就像是高级兼容属性一样。
此后会向cpu节点添加更多的属性,但我们首先要谈的是一些基本概念。
节点名称
我们值得花一些时间来谈论命名规范。每个节点都必须有一个名称,形式是<name>[@<unit-address>]。
<name>是一个简单的ascii字符串,长度可以达到31个字符。通常节点名称是依据它所代表的设备。例如一个3com以太网适配器应该使用的名称为ethernet,而不是3com509。
如果节点是用一个adress来描述设备的,那么就应该包括Unit-address。通常unit address是用来接入设备的原地址,并列入到节点的reg属性。我们将在下文提及reg属性。
同级节点必须有唯一名称,但是只要地址不同,不只一个节点会使用相同的属名(例如,serial@101f1000 & serial@101f2000)。
设备
系统中的每个设备都由一个device tree节点所代表。下一步是要为每个设备用节点填充树形结构。现在新节点将会为空,直到我们了解如何处理地址范围和终端请求。
/ {
compatible = "acme,coyotes-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
};
cpu@1 {
compatible = "arm,cortex-a9";
};
};
serial@101F0000 {
compatible = "arm,pl011";
};
serial@101F2000 {
compatible = "arm,pl011";
};
gpio@101F3000 {
compatible = "arm,pl061";
};
interrupt-controller@10140000 {
compatible = "arm,pl190";
};
spi@10115000 {
compatible = "arm,pl022";
};
external-bus {
ethernet@0,0 {
compatible = "smc,smc91c111";
};
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
rtc@58 {
compatible = "maxim,ds1338";
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
};
};
};
此树形结构中,在系统里为每个设备添加了一个节点,并且层级结构也反映出设备是如何与系统相关联的。例如外总线的设备是外总线节点的子节点,i2C设备是i2c总线控制器节点的子节点。通常系统的层级结构代表了CPU的视图。
此时这个树形结构是无效的。它缺失设备间相关联的信息。过后会将这些数据添加上去。需要注意的是:
每个设备节点都有一个compatible属性
Flash节点在兼容属性中有两个字符串。
如前文所提,节点名称代表了设备类型,并不是详细型号。
了解compatible属性
树形结构中代表一个设备的每个节点都要求拥有compatible属性。Compatible是操作系统决定使用哪个设备驱动程序的关键。
Compatible是字符串列表。列表中第一个字符串“<manufacturer>,<model>”指定了节点代表的确切的设备。以下字符串代表了其它设备。
例如,位于芯片上的Freescale MPC8349系统拥有一个串行设备,此设备是National Semiconductor ns16550注册界面的工具。MPC8349串口设备的compatible属性应该为:compatible = "fsl,mpc8349-uart", "ns16550"。此时fsl,mpc8349-uart指定了确切的设备,ns16550表示与National Semiconductor 16550 UART兼容。
注:ns16550由于历史原因没有制造商前缀。所有新的compatible值都应该使用制造商前缀。
此练习允许已存在设备驱动程序绑定到设备上,但仍然是唯一指定确切的硬件。
注:不要使用wildcard compatible值,如:fsl,mpc83xx-uart或类似的。如果它没有及时进行变更,Silicon供应商的变更会打破你的wildcard假设。你可以选择一个特定的silicon执行程序并将所有后期的silicon与之相兼容。
下一步是描述CPU。一个名为“cpus”的container node为每个CPU添加了一个子节点。在此情况下此系统为源于ARM的双核Cortex A9系统。
/ {
compatible = "acme,coyotes-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
};
cpu@1 {
compatible = "arm,cortex-a9";
};
};
};
在每个cpu节点的兼容属性是字符串<manufacturer>,<model>,用以指定确切的cpu,就像是高级兼容属性一样。
此后会向cpu节点添加更多的属性,但我们首先要谈的是一些基本概念。
节点名称
我们值得花一些时间来谈论命名规范。每个节点都必须有一个名称,形式是<name>[@<unit-address>]。
<name>是一个简单的ascii字符串,长度可以达到31个字符。通常节点名称是依据它所代表的设备。例如一个3com以太网适配器应该使用的名称为ethernet,而不是3com509。
如果节点是用一个adress来描述设备的,那么就应该包括Unit-address。通常unit address是用来接入设备的原地址,并列入到节点的reg属性。我们将在下文提及reg属性。
同级节点必须有唯一名称,但是只要地址不同,不只一个节点会使用相同的属名(例如,serial@101f1000 & serial@101f2000)。
设备
系统中的每个设备都由一个device tree节点所代表。下一步是要为每个设备用节点填充树形结构。现在新节点将会为空,直到我们了解如何处理地址范围和终端请求。
/ {
compatible = "acme,coyotes-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
};
cpu@1 {
compatible = "arm,cortex-a9";
};
};
serial@101F0000 {
compatible = "arm,pl011";
};
serial@101F2000 {
compatible = "arm,pl011";
};
gpio@101F3000 {
compatible = "arm,pl061";
};
interrupt-controller@10140000 {
compatible = "arm,pl190";
};
spi@10115000 {
compatible = "arm,pl022";
};
external-bus {
ethernet@0,0 {
compatible = "smc,smc91c111";
};
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
rtc@58 {
compatible = "maxim,ds1338";
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
};
};
};
此树形结构中,在系统里为每个设备添加了一个节点,并且层级结构也反映出设备是如何与系统相关联的。例如外总线的设备是外总线节点的子节点,i2C设备是i2c总线控制器节点的子节点。通常系统的层级结构代表了CPU的视图。
此时这个树形结构是无效的。它缺失设备间相关联的信息。过后会将这些数据添加上去。需要注意的是:
每个设备节点都有一个compatible属性
Flash节点在兼容属性中有两个字符串。
如前文所提,节点名称代表了设备类型,并不是详细型号。
了解compatible属性
树形结构中代表一个设备的每个节点都要求拥有compatible属性。Compatible是操作系统决定使用哪个设备驱动程序的关键。
Compatible是字符串列表。列表中第一个字符串“<manufacturer>,<model>”指定了节点代表的确切的设备。以下字符串代表了其它设备。
例如,位于芯片上的Freescale MPC8349系统拥有一个串行设备,此设备是National Semiconductor ns16550注册界面的工具。MPC8349串口设备的compatible属性应该为:compatible = "fsl,mpc8349-uart", "ns16550"。此时fsl,mpc8349-uart指定了确切的设备,ns16550表示与National Semiconductor 16550 UART兼容。
注:ns16550由于历史原因没有制造商前缀。所有新的compatible值都应该使用制造商前缀。
此练习允许已存在设备驱动程序绑定到设备上,但仍然是唯一指定确切的硬件。
注:不要使用wildcard compatible值,如:fsl,mpc83xx-uart或类似的。如果它没有及时进行变更,Silicon供应商的变更会打破你的wildcard假设。你可以选择一个特定的silicon执行程序并将所有后期的silicon与之相兼容。
寻址的运行
可寻址的设备是使用以下属性将地址信息编入device tree的:
n Reg
n #address-cells
n #size-cells
每个可寻址设备都会得到一个reg,它是一个元组列表:reg = <address1 length1 [address2 length2] [address3 length3] ... >。每个元组代表设备的地址范围。每个address值是一个或多个被叫做cells的32bit整数的列表。类似的,长度值可以为cells列表或为空。
由于地址或长度字段都是可变的,母节点的#address-cells和#size-cells属性就表示在每个字段中有多少个cells。也就是说想要准确的解释一个reg属性则需要有母节点的#address-cells和#size-cells值。想要了解这些是如何运行的,让我们将寻址属性添加到device tree样本,从CPUs开始。
CPU寻址
当谈论到寻址是,CPU节点是最简单的例子。每个CPU都有一个单独且唯一的ID,并且没有size与CPU ids相关联。
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
};
cpu@1 {
compatible = "arm,cortex-a9";
reg = <1>;
};
};
在cpus节点,#address-cells被设置成了1,#size-cells被设置成了0。这是说子reg值是单独的uint32,它用无大小字段表示地址。在此情况下,这两个cpu分配到的地址为0和1。Cpu节点的#size-cells是0因为每个cpu只分配到了一个单独的地址。
你仍然需要注意reg值班需要与节点名的值相匹配。按照惯例,如果一个节点有一个reg属性,那么这个节点名称必须包括unit-address,这是reg属性的第一个address值。
内存映射设备
与在cpu节点中单独的address值不同,内存映射设备被分配了一系列将要响应的地址。#size-cells用来表示在每个子reg元组中长度字段的大小。在以下示例中,每个address值为1 cell(32 bits),每个长度值也是1 cell,这在32 bit系统是比较典型的。64 bit设备也许会为#address-cells和#size-cells使用数值2,在device tree中获取64 bit addressing。
/ {
#address-cells = <1>;
#size-cells = <1>;
...
serial@101f0000 {
compatible = "arm,pl011";
reg = <0x101f0000 0x1000 >;
};
serial@101f2000 {
compatible = "arm,pl011";
reg = <0x101f2000 0x1000 >;
};
gpio@101f3000 {
compatible = "arm,pl061";
reg = <0x101f3000 0x1000
0x101f4000 0x0010>;
};
interrupt-controller@10140000 {
compatible = "arm,pl190";
reg = <0x10140000 0x1000 >;
};
spi@10115000 {
compatible = "arm,pl022";
reg = <0x10115000 0x1000 >;
};
...
};
非内存映射设备
处理器总线的其它设备为非内存映射设备。他们有地址范围,但不能被CPU直接寻址。母设备的驱动程序将代替CPU进行间接访问。
以i2c设备为例,每个设备都分配了一个地址,但没有长度或范围与之相匹配。这与CPU地址分配很相似。
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
#address-cells = <1>;
#size-cells = <0>;
reg = <1 0 0x1000>;
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
};
};
可寻址的设备是使用以下属性将地址信息编入device tree的:
n Reg
n #address-cells
n #size-cells
每个可寻址设备都会得到一个reg,它是一个元组列表:reg = <address1 length1 [address2 length2] [address3 length3] ... >。每个元组代表设备的地址范围。每个address值是一个或多个被叫做cells的32bit整数的列表。类似的,长度值可以为cells列表或为空。
由于地址或长度字段都是可变的,母节点的#address-cells和#size-cells属性就表示在每个字段中有多少个cells。也就是说想要准确的解释一个reg属性则需要有母节点的#address-cells和#size-cells值。想要了解这些是如何运行的,让我们将寻址属性添加到device tree样本,从CPUs开始。
CPU寻址
当谈论到寻址是,CPU节点是最简单的例子。每个CPU都有一个单独且唯一的ID,并且没有size与CPU ids相关联。
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
};
cpu@1 {
compatible = "arm,cortex-a9";
reg = <1>;
};
};
在cpus节点,#address-cells被设置成了1,#size-cells被设置成了0。这是说子reg值是单独的uint32,它用无大小字段表示地址。在此情况下,这两个cpu分配到的地址为0和1。Cpu节点的#size-cells是0因为每个cpu只分配到了一个单独的地址。
你仍然需要注意reg值班需要与节点名的值相匹配。按照惯例,如果一个节点有一个reg属性,那么这个节点名称必须包括unit-address,这是reg属性的第一个address值。
内存映射设备
与在cpu节点中单独的address值不同,内存映射设备被分配了一系列将要响应的地址。#size-cells用来表示在每个子reg元组中长度字段的大小。在以下示例中,每个address值为1 cell(32 bits),每个长度值也是1 cell,这在32 bit系统是比较典型的。64 bit设备也许会为#address-cells和#size-cells使用数值2,在device tree中获取64 bit addressing。
/ {
#address-cells = <1>;
#size-cells = <1>;
...
serial@101f0000 {
compatible = "arm,pl011";
reg = <0x101f0000 0x1000 >;
};
serial@101f2000 {
compatible = "arm,pl011";
reg = <0x101f2000 0x1000 >;
};
gpio@101f3000 {
compatible = "arm,pl061";
reg = <0x101f3000 0x1000
0x101f4000 0x0010>;
};
interrupt-controller@10140000 {
compatible = "arm,pl190";
reg = <0x10140000 0x1000 >;
};
spi@10115000 {
compatible = "arm,pl022";
reg = <0x10115000 0x1000 >;
};
...
};
非内存映射设备
处理器总线的其它设备为非内存映射设备。他们有地址范围,但不能被CPU直接寻址。母设备的驱动程序将代替CPU进行间接访问。
以i2c设备为例,每个设备都分配了一个地址,但没有长度或范围与之相匹配。这与CPU地址分配很相似。
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
#address-cells = <1>;
#size-cells = <0>;
reg = <1 0 0x1000>;
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
};
};
范围(地址转换)
我们已经讨论过如何向设备分配地址,但此时这些地址只是本地设备节点,还没有说明如何从那些地址里映射到cpu可以使用的地址。
根节点经常描述地址空间的CPU视图。根节点的子节点已经使用了CPU的address domain,所以不需要任何明确的映射。例如,serial@101f0000设备被直接分配了地址0x101f0000。
根节点的非直接子节点是无法使用CPU的address domain的。为了在deivce tree获取内存映射地址必须指定如何从一个域名将地址转换到另一个。Ranges属性就用于此目的。
以下是添加了ranges属性的device tree示例。
/ {
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";
#address-cells = <1>;
#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表格的是包含子地址的元组,母地址和子地址空间的范围大小。每个字段的大小都由获取的子地址的#address-cells值,母地址的#address-cell值和子地址的#size-cells值而定。以外部总线为例,子地址是2 cells,母地址是1 cell,大小也为1 cell。转换三个ranges:
Offset 0 from chip select 0 被映射到address range 0x10100000..0x1010ffff
Offset 0 from chip select 1被映射到address range 0x10160000..0x1016ffff
Offset 0 from chip select 2被映射到 address range 0x30000000..0x10000000
轮流的,如果母地址或子地址空间是唯一的,那么一个节点可以添加一个空ranges属性。一个空ranges属性的出现表示位于子地址空间的地址被1:1的映射到母地址空间。
你也许要问为什么当地址可以被1:1映射的时候还要使用地址转换。一些总线(如PCI)拥有完全不同的地址空间,而这些地址空间细节需要出现在操作系统。其它则拥有DMA驱动程序,这些程序需要在总线了解真正的地址。有时设备需要被集合,因为他们都分享相同的软件可编程物理地址映射。
你需要注意的是在i2c@1,0节点中没有ranges属性。原因是与外部总线不同,i2c总线的设备在cpu地址域名没有内存映射。而是cpu通过i2c@1,0设备间接访问rtc@58设备。缺少ranges属性意味着一个人设备除了线设备之外,不允许任何设备进行直接访问。
如何中断运行
与遵循自然树结构的地址范围转换不同,中断信号可以源自于或终止于一个机器的任何设备。与树结构中自然传递的设备地址不同,中断信号只在树结构中的独立节点间传递。四个属笥用于描述中断联接:
Interrupt-controller-一个空属性表明一个节点作为一个接收中断信号的设备
#interrupt-cell-这是中断控制器节点的一个属性。它代表此中断控制器的interrupt specifier有多少cells。
Interrupts-包括interrupt specifier列表设备的一个属性,设备上每个中断输出信号都有一个。
一个interrupt specifier数据的是一个或多个cell,此数据表示此设备附属于哪个中断输入。大多数设备只有一个单独的中断输出,如下面示例,但设备上也可能有多个中断输出。一个中断说明符表达的意思完全取决于与中断控制器设备的捆绑。每个中断控制器都可以决定唯一识别中断输入的所需的cell量。
以下代码向我们Coyote’s Revenge样机添加了中断连接:
/ {
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>;
};
};
};
注意事项:
此样机拥有一个单独的中断控制器,interrupt-controller@10140000。
“intc:”标签被添加到了中断控制器节点,并且用于向根节点的interrupt-parent属性分配一个phandle。此interrupt-parent值成了系统的默认值。
每个设备都使用一个中断属性来指定一个不同的输入线
#interrupt-cell是2,所以每个中断说明符拥有2个cell。
我们已经讨论过如何向设备分配地址,但此时这些地址只是本地设备节点,还没有说明如何从那些地址里映射到cpu可以使用的地址。
根节点经常描述地址空间的CPU视图。根节点的子节点已经使用了CPU的address domain,所以不需要任何明确的映射。例如,serial@101f0000设备被直接分配了地址0x101f0000。
根节点的非直接子节点是无法使用CPU的address domain的。为了在deivce tree获取内存映射地址必须指定如何从一个域名将地址转换到另一个。Ranges属性就用于此目的。
以下是添加了ranges属性的device tree示例。
/ {
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";
#address-cells = <1>;
#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表格的是包含子地址的元组,母地址和子地址空间的范围大小。每个字段的大小都由获取的子地址的#address-cells值,母地址的#address-cell值和子地址的#size-cells值而定。以外部总线为例,子地址是2 cells,母地址是1 cell,大小也为1 cell。转换三个ranges:
Offset 0 from chip select 0 被映射到address range 0x10100000..0x1010ffff
Offset 0 from chip select 1被映射到address range 0x10160000..0x1016ffff
Offset 0 from chip select 2被映射到 address range 0x30000000..0x10000000
轮流的,如果母地址或子地址空间是唯一的,那么一个节点可以添加一个空ranges属性。一个空ranges属性的出现表示位于子地址空间的地址被1:1的映射到母地址空间。
你也许要问为什么当地址可以被1:1映射的时候还要使用地址转换。一些总线(如PCI)拥有完全不同的地址空间,而这些地址空间细节需要出现在操作系统。其它则拥有DMA驱动程序,这些程序需要在总线了解真正的地址。有时设备需要被集合,因为他们都分享相同的软件可编程物理地址映射。
你需要注意的是在i2c@1,0节点中没有ranges属性。原因是与外部总线不同,i2c总线的设备在cpu地址域名没有内存映射。而是cpu通过i2c@1,0设备间接访问rtc@58设备。缺少ranges属性意味着一个人设备除了线设备之外,不允许任何设备进行直接访问。
如何中断运行
与遵循自然树结构的地址范围转换不同,中断信号可以源自于或终止于一个机器的任何设备。与树结构中自然传递的设备地址不同,中断信号只在树结构中的独立节点间传递。四个属笥用于描述中断联接:
Interrupt-controller-一个空属性表明一个节点作为一个接收中断信号的设备
#interrupt-cell-这是中断控制器节点的一个属性。它代表此中断控制器的interrupt specifier有多少cells。
Interrupts-包括interrupt specifier列表设备的一个属性,设备上每个中断输出信号都有一个。
一个interrupt specifier数据的是一个或多个cell,此数据表示此设备附属于哪个中断输入。大多数设备只有一个单独的中断输出,如下面示例,但设备上也可能有多个中断输出。一个中断说明符表达的意思完全取决于与中断控制器设备的捆绑。每个中断控制器都可以决定唯一识别中断输入的所需的cell量。
以下代码向我们Coyote’s Revenge样机添加了中断连接:
/ {
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>;
};
};
};
注意事项:
此样机拥有一个单独的中断控制器,interrupt-controller@10140000。
“intc:”标签被添加到了中断控制器节点,并且用于向根节点的interrupt-parent属性分配一个phandle。此interrupt-parent值成了系统的默认值。
每个设备都使用一个中断属性来指定一个不同的输入线
#interrupt-cell是2,所以每个中断说明符拥有2个cell。
设备具体数据
除了通用的属性以外,任意属性和子节点都能添加到节点。任何操作系统需要的数据只要有一些规则就可以进行添加。
首先,新device-specific属性名称应该使用一个制造商前缀,这样它们才不会与已存在标准属性名称相冲突。
其次,属性和子节点代表的意义必须有绑定的说明,这样设备驱动程序开发者才能了解如何中断数据。一个绑定的文档能常包括特殊兼容值含义,应该有哪些属性,可能存在的子节点和代表的是什么设备。每个唯一的compatible值都应该有它自己的绑定文档。新设备的绑定文档应该在wiki。
第三,将新的绑定文档放在devicetree-discuss@lists.ozlabs.org邮件列表中。查看新的绑定文档来获取将来可能会发生的常见问题。
特殊节点
aliases节点
一个特定的节点通常由全路径引用,如/external-bus/ethernet@0,0,但当用户想知道具体内容的时候显得太累赘,“哪个设备是eth0?”。Aliases邛咪可以用来为设备全路径分本一个短的ALIAS。如:
aliases {
ethernet0 = ð0;
serial0 = &serial0;
};
当为设备分配一个标识符的时候,操作系统更倾向于使用aliases。
你会注意到这里使用的新句法。Property = &label;句法分配了由用作字符串属性的label引用的完整节点路径。这与phandle = < &label >不同;
chosen节点
chosen节点不代表一个真正的设备,但功能与在固件和操作系统间传递数据的地点一样,如根参数。位于chosen节点的数据不代表硬件。具有代表性的是在in.dts源文件中值为空。
在我们的示例系统中,固件也许添加了以下的chosen节点:
chosen {
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};
高级主题
高级样机
现在我们了解了基本定义,那么我们就向样机添加一些固件来讨论一些更加复杂的情况。
高级样机使用控制寄存器内存映射0X101180000来添加PCI主机桥,并执行BARs来启动以上的地址0x80000000。
给出我们已经了解的关于device tree的内容。我们就可以从以下附加的节点来描述PCI主机桥了。
pci@10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
};
PCI主机桥
此节描述了Host/PCI桥节点。
注意的是,一些关于PCI的基本知识。这不是关于PCI的指南,如果你需要一些更深入的信息,请读[1]。
PCI总线编号
每个PCI总线段都有唯一编号,并且总线编号是通过使用bus-ranges属性显示出来的,此属性包括2个cell。第一个cell会将分配到此节点的总线编号,第二个cell会给出任何一个PCI总线下级当中最高线线编号。
此样机拥有一个单独的pci总线,所以两个cell都为0。
pci@0x10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
bus-ranges = <0 0>;
};
PCI地址转换
与之前讨论过的本地总线相似,PCI地址空间从CPU地址空间中完全分离出去了,所以需要从PCI址址到CPU地址进行地址转换。通常情况会使用rang,#address-cells和size-cells属性来完成。
pci@0x10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
bus-ranges = <0 0>;
#address-cells = <3>
#size-cells = <2>;
ranges = <0x42000000 0 0x80000000 0x80000000 0 0x20000000
0x02000000 0 0xa0000000 0xa0000000 0 0x10000000
0x01000000 0 0x00000000 0xb0000000 0 0x01000000>;
};
正如你所看到的,子地址(PCI地址)使用了3个cell,PCI range被编入了2个cell。首先的问题可能是为什么我们需要3个32 bit cell来指定PCI地址。这三个cell被标记成phys.hi.mid和phys.low。
phys.hi cell: npt000ss bbbbbbbb dddddfff rrrrrrrr
phys.mid cell: hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh
phys.low cell: llllllll llllllll llllllll llllllll
PCI地址宽度是64 bi,并且都编入到phys.mid和phys.low。然而真正有趣的事是在1 bit字段的phys.high:
n: 可再定址的地区标记(在这里无效)
p: 可预取地址标记
t: 别名地址标记(此处无效)
ss: 空间代码
00: 配置空间
01: I/O空间
10:32 bit 内存空间
11:64 bit 内存空间
Bbbbbbbb: PCI总线编号。PCI也许是分等级的结构。所以我们也许需要有PCI/PCI桥来定义子总线。
Ddddd: 设备编号,通常与IDSEL信号连接相匹配。
Fff: 功能编号。用于多功能PCI设备。
Rrrrrrr: 寄存器编号,用于设置周期。
为了进行PCI地址转换,最重要的字段是p和ss。位于phys.hi的P和ss的值确定了访问哪个PCI地址空间。所以查看我们的ranges属性,我们有3个区域:
一个32 bit可预取内存区域,它位于512MByte size的PCI地址0x80000000,并且将映射到主机CPU的地址0x80000000。
一个32 bit非可预取内存区域,它位于256MByte size的PCI地址0xa0000000,并且将映射到主机CPU的地址0xa0000000。
一个I/O区域,它位于16MByte size的PCI地址0x0000000,并且将会映射到主机CPU地址0x0000000。
此phys.hi位字段的存在表示一个操作系统需要知道节点代表了一个PCI桥,这样它才能忽略不相干的字段实现转换。一个OS将在PCI总线节点查询字符串“pci”来决定它是否需要掩饰额外的字段。
除了通用的属性以外,任意属性和子节点都能添加到节点。任何操作系统需要的数据只要有一些规则就可以进行添加。
首先,新device-specific属性名称应该使用一个制造商前缀,这样它们才不会与已存在标准属性名称相冲突。
其次,属性和子节点代表的意义必须有绑定的说明,这样设备驱动程序开发者才能了解如何中断数据。一个绑定的文档能常包括特殊兼容值含义,应该有哪些属性,可能存在的子节点和代表的是什么设备。每个唯一的compatible值都应该有它自己的绑定文档。新设备的绑定文档应该在wiki。
第三,将新的绑定文档放在devicetree-discuss@lists.ozlabs.org邮件列表中。查看新的绑定文档来获取将来可能会发生的常见问题。
特殊节点
aliases节点
一个特定的节点通常由全路径引用,如/external-bus/ethernet@0,0,但当用户想知道具体内容的时候显得太累赘,“哪个设备是eth0?”。Aliases邛咪可以用来为设备全路径分本一个短的ALIAS。如:
aliases {
ethernet0 = ð0;
serial0 = &serial0;
};
当为设备分配一个标识符的时候,操作系统更倾向于使用aliases。
你会注意到这里使用的新句法。Property = &label;句法分配了由用作字符串属性的label引用的完整节点路径。这与phandle = < &label >不同;
chosen节点
chosen节点不代表一个真正的设备,但功能与在固件和操作系统间传递数据的地点一样,如根参数。位于chosen节点的数据不代表硬件。具有代表性的是在in.dts源文件中值为空。
在我们的示例系统中,固件也许添加了以下的chosen节点:
chosen {
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};
高级主题
高级样机
现在我们了解了基本定义,那么我们就向样机添加一些固件来讨论一些更加复杂的情况。
高级样机使用控制寄存器内存映射0X101180000来添加PCI主机桥,并执行BARs来启动以上的地址0x80000000。
给出我们已经了解的关于device tree的内容。我们就可以从以下附加的节点来描述PCI主机桥了。
pci@10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
};
PCI主机桥
此节描述了Host/PCI桥节点。
注意的是,一些关于PCI的基本知识。这不是关于PCI的指南,如果你需要一些更深入的信息,请读[1]。
PCI总线编号
每个PCI总线段都有唯一编号,并且总线编号是通过使用bus-ranges属性显示出来的,此属性包括2个cell。第一个cell会将分配到此节点的总线编号,第二个cell会给出任何一个PCI总线下级当中最高线线编号。
此样机拥有一个单独的pci总线,所以两个cell都为0。
pci@0x10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
bus-ranges = <0 0>;
};
PCI地址转换
与之前讨论过的本地总线相似,PCI地址空间从CPU地址空间中完全分离出去了,所以需要从PCI址址到CPU地址进行地址转换。通常情况会使用rang,#address-cells和size-cells属性来完成。
pci@0x10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
bus-ranges = <0 0>;
#address-cells = <3>
#size-cells = <2>;
ranges = <0x42000000 0 0x80000000 0x80000000 0 0x20000000
0x02000000 0 0xa0000000 0xa0000000 0 0x10000000
0x01000000 0 0x00000000 0xb0000000 0 0x01000000>;
};
正如你所看到的,子地址(PCI地址)使用了3个cell,PCI range被编入了2个cell。首先的问题可能是为什么我们需要3个32 bit cell来指定PCI地址。这三个cell被标记成phys.hi.mid和phys.low。
phys.hi cell: npt000ss bbbbbbbb dddddfff rrrrrrrr
phys.mid cell: hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh
phys.low cell: llllllll llllllll llllllll llllllll
PCI地址宽度是64 bi,并且都编入到phys.mid和phys.low。然而真正有趣的事是在1 bit字段的phys.high:
n: 可再定址的地区标记(在这里无效)
p: 可预取地址标记
t: 别名地址标记(此处无效)
ss: 空间代码
00: 配置空间
01: I/O空间
10:32 bit 内存空间
11:64 bit 内存空间
Bbbbbbbb: PCI总线编号。PCI也许是分等级的结构。所以我们也许需要有PCI/PCI桥来定义子总线。
Ddddd: 设备编号,通常与IDSEL信号连接相匹配。
Fff: 功能编号。用于多功能PCI设备。
Rrrrrrr: 寄存器编号,用于设置周期。
为了进行PCI地址转换,最重要的字段是p和ss。位于phys.hi的P和ss的值确定了访问哪个PCI地址空间。所以查看我们的ranges属性,我们有3个区域:
一个32 bit可预取内存区域,它位于512MByte size的PCI地址0x80000000,并且将映射到主机CPU的地址0x80000000。
一个32 bit非可预取内存区域,它位于256MByte size的PCI地址0xa0000000,并且将映射到主机CPU的地址0xa0000000。
一个I/O区域,它位于16MByte size的PCI地址0x0000000,并且将会映射到主机CPU地址0x0000000。
此phys.hi位字段的存在表示一个操作系统需要知道节点代表了一个PCI桥,这样它才能忽略不相干的字段实现转换。一个OS将在PCI总线节点查询字符串“pci”来决定它是否需要掩饰额外的字段。