
2、于是从2011年3月开始,内核在PowerPC、ARM等体系里正式打算使用设备树。以ARM体系为例,加入设备树的版本就是v3.1,可以在arch/arm/boot/目录下看到dts目录的出现。
Linux and the Device TreeLinux内核设备树数据使用模型。
Open Firmware Device Tree (DT) 是一个数据结构,也是一种描述硬件的语言。准确地说,它是一种能被 *** 作系统解析的描述硬件的语言,这样 *** 作系统就不需要把硬件平台的细节在代码中写死。
从结构上来说,DT是一个树形结构,或者有名结点组成的非循环图,结点可能包含任意数量的有名属性,有名属性又可以包含任意数量的数据。同样存在一种机制,可以创建从一个结点到正常树形结构之外的链接。
从概念上讲,一套通用的使用方法,即bindings。Bindings定义了数据如何呈现在设备树中,怎样描述典型的硬件特性,包括数据总线,中断线,GPIO连接以及外设等。
尽可能多的硬件被描述从而使得已经存在的bindings最大化地使用源代码,但是由于属性名和结点名是简单字符串, 可以通过定义新结点和属性的方式很方便地扩展已经存在的bindings或者创建一个新的binding。在没有认真了解过已经存在的bindings的情况下,创建一个新的binding要慎之又慎。对于I2C总线,通常有两种不同的,互不相容的bindings出现,就是因为新的binding创建时没有研究I2C设备是如何在当前系统中被枚举的。
1. 历史
略
2. 数据模型
请参考Device Tree Usage章节
2.1 High Level View
必须要认识到的是,DT是一个描述硬件的数据结构。它并没有什么神奇的地方,也不能把所有硬件配置的问题都解决掉。它只是提供了一种语言,将硬件配置从Linux Kernel支持的board and device driver中提取出来。DT使得board和device变成数据驱动的,它们必须基于传递给内核的数据进行初始化,而不是像以前一样采用hard coded的方式。
观念上说,数据驱动平台初始化可以带来较少的代码重复率,使得单个内核映像能够支持很多硬件平台。
Linux使用DT的三个主要原因:
1) 平台识别 (Platform Identification)
2) 实时配置 (Runtime Configuration)
3) 设备植入 (Device Population)
2.2 平台识别
第一且最重要的是,内核使用DT中的数据去识别特定机器。最完美的情况是,内核应该与特定硬件平台无关,因为所有硬件平台的细节都由设备树来描述。然而,硬件平台并不是完美的,所以内核必须在早期初始化阶段识别机器,这样内核才有机会运行特定机器相关的初始化序列。
大多数情况下,机器识别是与设备树无关的,内核通过机器的核心CPU或者SOC来选择初始化代码。以ARM平台为例,setup_arch()会调用setup_machine_fdt(),后者遍历machine_desc链表,选择最匹配设备树数据的machine_desc结构体。它是通过查找设备树根结点的compatible属性并与machine_desc->dt_compat进行比较来决定哪一个machine_desc结构体是最适合的。
Compatible属性包含一个有序的字符串列表,它以确切的机器名开始,紧跟着一个可选的board列表,从最匹配到其他匹配类型。以TI BeagleBoard的compatible属性为例,BeagleBoard xM Board可能描述如下:
compatible = "ti,omap3-beagleboard", "ti,omap3450", "ti,omap3"
compatible = "ti,omap3-beagleboard-xm", "ti,omap3450", "ti,omap3"
在这里,”ti, omap3-beagleboard-xm”是最匹配的模型,"ti,omap3450"次之,"ti,omap3"再次之。
机敏的读者可能指出,Beagle xM也可以声明匹配"ti,omap3-beagleboard",但是要注意的是,板级层次上,两个机器之间的变化比较大,很难确定是否兼容。从顶层上来看,宁可小心也不要去声明一个board兼容另外一个。值得注意的情况是,当一个board承载另外一个,例如一个CPU附加在一个board上。(两种CPU支持同一个board的情况)
DTS (device tree source).dts文件是一种ASCII 文本格式的Device
Tree描述,此文本格式非常人性化,适合人类的阅读习惯。基本上,在ARM
Linux在,一个.dts文件对应一个ARM的machine,一般放置在内核的arch/arm/boot/dts/目录。由于一个SoC可能对应多个machine(一个SoC可以对应多个产品和电路板),势必这些.dts文件需包含许多共同的部分,Linux内核为了简化,把SoC公用的部分或者多个machine共同的部分一般提炼为.dtsi,类似于C语言的头文件。其他的machine对应的.dts就include这个.dtsi。譬如,对于VEXPRESS而言,vexpress-v2m.dtsi就被vexpress-v2p-ca9.dts所引用,
vexpress-v2p-ca9.dts有如下一行:
/include/
"vexpress-v2m.dtsi"
当然,和C语言的头文件类似,.dtsi也可以include其他的.dtsi,譬如几乎所有的ARM
SoC的.dtsi都引用了skeleton.dtsi。
.dts(或者其include的.dtsi)基本元素即为前文所述的结点和属性:
[plain] view
plaincopyprint?
/ {
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 {
}
}
}
/ {
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文件并没有什么真实的用途,但它基本表征了一个Device
Tree源文件的结构:
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"。
下面以一个最简单的machine为例来看如何写一个.dts文件。假设此machine的配置如下:
1个双核ARM
Cortex-A9 32位处理器;
ARM的local bus上的内存映射区域分布了2个串口(分别位于0x101F1000 和
0x101F2000)、GPIO控制器(位于0x101F3000)、SPI控制器(位于0x10170000)、中断控制器(位于0x10140000)和一个external
bus桥;
External bus桥上又连接了SMC SMC91111
Ethernet(位于0x10100000)、I2C控制器(位于0x10160000)、64MB NOR
Flash(位于0x30000000);
External bus桥上连接的I2C控制器所对应的I2C总线上又连接了Maxim
DS1338实时钟(I2C地址为0x58)。
其对应的.dts文件为:
[plain] view
plaincopyprint?
/ {
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>
}
}
}
/ {
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文件中,root结点"/"的compatible 属性compatible =
"acme,coyotes-revenge"定义了系统的名称,它的组织形式为:<manufacturer>,<model>。Linux内核透过root结点"/"的compatible
属性即可判断它启动的是什么machine。
在.dts文件的每个设备,都有一个compatible
属性,compatible属性用户驱动和设备的绑定。compatible
属性是一个字符串的列表,列表中的第一个字符串表征了结点代表的确切设备,形式为"<manufacturer>,<model>",其后的字符串表征可兼容的其他设备。可以说前面的是特指,后面的则涵盖更广的范围。如在arch/arm/boot/dts/vexpress-v2m.dtsi中的Flash结点:
[plain] view
plaincopyprint?
flash@0,00000000 {
compatible = "arm,vexpress-flash", "cfi-flash"
reg = <0 0x00000000 0x04000000>,
<1 0x00000000 0x04000000>
bank-width = <4>
}
flash@0,00000000 {
compatible = "arm,vexpress-flash", "cfi-flash"
reg = <0 0x00000000 0x04000000>,
<1 0x00000000 0x04000000>
bank-width = <4>
}
compatible属性的第2个字符串"cfi-flash"明显比第1个字符串"arm,vexpress-flash"涵盖的范围更广。
再比如,Freescale
MPC8349 SoC含一个串口设备,它实现了国家半导体(National Semiconductor)的ns16550
寄存器接口。则MPC8349串口设备的compatible属性为compatible = "fsl,mpc8349-uart",
"ns16550"。其中,fsl,mpc8349-uart指代了确切的设备, ns16550代表该设备与National Semiconductor
的16550
UART保持了寄存器兼容。
接下来root结点"/"的cpus子结点下面又包含2个cpu子结点,描述了此machine上的2个CPU,并且二者的compatible
属性为"arm,cortex-a9"。
注意cpus和cpus的2个cpu子结点的命名,它们遵循的组织形式为:<name>[@<unit-address>],<>中的内容是必选项,[]中的则为可选项。name是一个ASCII字符串,用于描述结点对应的设备类型,如3com
Ethernet适配器对应的结点name宜为ethernet,而不是3com509。如果一个结点描述的设备有地址,则应该给出@unit-address。多个相同类型设备结点的name可以一样,只要unit-address不同即可,如本例中含有cpu@0、cpu@1以及serial@101f0000与serial@101f2000这样的同名结点。设备的unit-address地址也经常在其对应结点的reg属性中给出。ePAPR标准给出了结点命名的规范。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)