基本概念
________________________________________________
为了理解怎样使用设备树,咱们从一个样品机开始而且搭建设备树来一步步描述它
样品机
考虑下面的假想机器(粗略地基于ARM Versatile),"Acme"制造,命名 “Coyote's Revenge":
- 32位 ARM CPU单核
- PLB粘附在内存映射串口上,spi总线控制器,i2c控制器,中断控制器以及外部总线桥
- 256MB SDRAM基址从0开始
- 2个串口,基址从0x101F1000,0X101F2000开始
- GPIO控制器,基址从0x101F3000开始
- SPI控制器,基址从0x10170000而且总线上挂载着下列设备:
- 外部总线桥而且总线上挂载着下列设备:
- SMC SMC91111以太网设备链接在基址从0x10100000开始的外部总线上
- i2c控制器,基址从0x10160000开始而且总线上挂载着以下设备:
- Maxim DS1338实时时钟。该器件响应0x58的从机地址。
- 64MB NOR flash,基址从0x30000000开始
初始结构
第一步是为机器制定框架结构。下面是一个合法的设备树所需的最小结构。在这个节点,你想一想要可以惟一地识别该机器
/
{
compatible
=
"acme,coyotes-revenge"
;
};
compatible制定系统的名称。它包含"<manufacture>,<model>"格式的字符串。准确地肯定器件型号是很是重要的,而且咱们须要包含厂商的名字来避免名字空间冲突。由于操做系统会使用compatible这个值来决定怎样在这个机器上运行,因此在这个属性中放入正确的值是很是重要的。
理论上来讲,compatible是一个OS须要惟一地识别机器所须要的惟一数据。若是全部机器的细节都是写死的,那么OS能够在顶层compatible属性中专门查找"acme,coyotes-revenge"。
CPUs
下一步是描述每一个CPU。一个命名为"cpus"的容器节点跟有对应每一个CPU的子节点。在这个例子中,系统是一个双核ARM Cortex A9的系统。
/
{
compatible
=
"acme,coyotes-revenge"
;
cpus
{
cpu@0
{
compatible
=
"arm,cortex-a9"
;
};
cpu@1
{
compatible
=
"arm,cortex-a9"
;
};
};
};
每一个CPU节点中的compatible属性是以<manufacturer>,<model>的格式肯定CPU型号的字符串,就像顶层的compatible属性那样。
更多的属性会在稍后添加到cpu节点中,可是咱们首先须要讨论更多基本的概念。
节点名字
咱们值得花一段时间讨论命名习惯。每个几点必需要有一个以<name>[@<unit-address>]形式的名字。
<name>是简单的ascii字符串而且长度最大能够到31个字符。一般来讲,节点是根据它所表明的设备类型来命名的。举个例子,3com公司的以太网适配器节点可能会使用ethernet做为它的名字,而不是3com509。
unit-address只有在节点描述含有地址的设备时会被包含进来。一般来讲,unit address是用来访问设备的基址,而且在节点的reg属性中被罗列出来。咱们稍后会在本文档中介绍reg属性。
兄弟节点必须被惟一地命名,不过对于不仅一个节点的状况,咱们一般会使用通用的名字,只要它们的地址不同就能够。(好比:seriali@101f1000 和serial@101f2000)。
若是须要更多节点命名方面的详细状况,请参考ePAPR规范的第2.21部分
设备
系统中的每个设备都由一个设备树节点来表明。下一步就是用每个设备对应的节点来填充树。如今来讲,新节点将会被置空直到咱们能够讨论地址范围以及终端是怎样安排的。
/
{
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属性中有两个字符串。阅读下一部分来了解为何会这样。
- 以前提到过,节点的名字反映了设备的类型,而不是特定的型号。状况ePAPR规范的2.2.2部分,规范中提到了一系列已经定义好了随处可能用到的通用节点名字
理解compatible属性
设备树中表明设备的每个节点必需要有compatible属性。compatible是操做系统用来决定哪一个设备驱动绑定哪一个设备的关键字。
compatible是字符串列表。列表中的第一个字符串以"<manufacturer>,<model>"的形式肯定了节点表明的设备。接下来的字符串表示该设备能够兼容的其余设备。
举个例子来讲,Freescale MPC8349 片上系统(SoC)有一个串行设备实现了国家半导体 ns16550寄存器接口。MPC8349穿行设备的compatible属性所以应该是:compatible = "fsl,mpc8349-uart","ns16550"。在这个例子中,fsl,mpc8349-uart肯定了设备而且ns16550表示它在寄存器级别兼容国家半导体16550 UART。
注意:ns16550没有厂商前缀纯粹是由于历史缘由(IBM-PC/AT吧..)。全部新的compatible值应该使用厂商前缀。
该操做容许现存的设备驱动绑定到更新的设备上,不过它仍然惟一地识别确切的硬件。
警告:不要在compatible值中使用通配符,好比"fsl,mpc83xx-uart"或者相似地。硅片厂商老是会作一些打破你通配符假设的变化,到那时再改变就为时已晚了。相反,选择一个特定的硅片实现而且使全部随后的硅片与之兼容。
寻址是如何工做的
________________________________________________
可寻址的设备使用下面的属性来将地址信息编码进入设备树:
- reg
- #address-cells
- #size-cells
每个可寻址的设备获取的reg,是以reg = <address1 length1 [address2 length2] [address3 length3] ...>形式的表格元组。每个元组表明设备所使用的地址范围。每个地址值是被称为cells的一个或者多个32位整数的列表。类似地,长度值既能够是cells的列表,也能够是空的。
由于地址和长度域都是长度可变的,因此父节点中的#address-cells和#size-cells属性是用来讲明每个域中有多少个cells。换句话说,正确地翻译一个reg属性须要父节点的#address-cells和#size-cells值。为了看到这一切是怎样工做的,让咱们将寻址属性添加到样例设备树中,首先从CPU开始
CPU寻址
讨论到寻址时,CPU节点表明最简单的状况。每个CPU被赋予了一个惟一的ID而且CPU的ID没有关联的尺寸。
cpus
{
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
};
cpu@1 {
compatible = "arm,cortex-a9";
reg = <1>;
};
};
cpu节点中,#address-cells被设置为1,#size-cells被设置为0.这意味着子节点的reg值是表明地址且没有size域的单个32位无符号整数。在这个例子中,2个cpu被赋予了地址0和1。cpu节点的#size-cells是0,由于cpu仅仅被赋予了单个地址。
你能注意到reg值匹配节点名字中的值。按照惯例,若是一个节点具备reg属性,这个节点必须包含单元地址,也就是reg属性中的第一个地址值。
内存映射设备
不一样于cpu节点中找到的单地址值,内存映射设备被分配了它会响应的一个地址范围。#size-cells用来讲明每个子节点中的reg元组有多长。接下来的例子中,每个地址值都是一个单元的(32位),每个长度值也是一个单元,即典型的32位系统。64位系统能够把#address-cells和#size-cells赋值为2从而在设备设备树中获得64位寻址。
/
{
#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 >;
};
...
};
每个设备都被分配了一个基地址,以及它被分配的区域的尺寸。本例中的GPIO设备地址被分配了两个地址范围:0x101f3000...0x101f3fff 和0x101f4000..0x101f400f。
有一些设备挂载在具备不一样寻址策略的总线上。举个例子来讲,一个设备能够被链接到具备独立片选信号的外部总线上。由于每个父节点定义了它子节点的寻址域,因此咱们能够从最佳描述系统的角度来选择地址映射方案。下面的代码显示了链接到外部总线上的设备的地址分配状况,而且这些外部总线具备编码如地址的片选数字。
external
-
bus
{
#address-cells = <2>
#size-cells = <1>;
ethernet@0
,
0
{
compatible
=
"smc,smc91c111"
;
reg = <0 0 0x1000>;
};
i2c@1
,
0
{
compatible
=
"acme,a1234-i2c-bus"
;
reg = <1 0 0x1000>;
rtc@58
{
compatible
=
"maxim,ds1338"
;
};
};
flash@2
,
0
{
compatible
=
"samsung,k8f1315ebm"
,
"cfi-flash"
;
reg = <2 0 0x4000000>;
};
};
外部总线使用了2个单元的地址值。一个是片选数字,另外一个是片选基地址的偏移。长度域仍旧是单个单元,由于只有地址的偏移部分须要一个范围。所以在本例中,每个reg入口都包含了3个单元:片选数字,偏移以及长度。
由于地址域被包含在节点以及它的子节点中,因此父节点能够自由定义任何总线上可行的寻址方案。直接父节点以外的节点一般不须要考虑本地寻址域,而且为了从一个域到另外一个域,地址必须被映射。
非内存映射设备
其余设备并无内存映射在处理器总线上。它们能够有寻址范围,可是它们并不能直接被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的地址域,是、所以不须要任何显示地映射。举个例子来讲,serial@101f0000设备是直接被分配了0x101f0000地址。
那些不是根节点的直接子节点的节点不能使用CPU的地址域。为了获得一个内存映射的地址,设备树必须制定如何从一个域地址转换到另外一个域。ranges属性就是用于这个目的的。
这里是添加了ranges属性的设备树例子。
/ {
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-cells值以及子节点的#size-cells值决定。对于本例子中的外部总线,子节点地址是2个单元,父节点地址是1个单元,大小也是一个单元的。三个ranges是这样被转换的:
- 从片选0处偏移0开始的地方被映射到地址范围0x10100000...0x1010ffff
- 从片选1处偏移0开始的地方被映射到地址范围0x10160000...0x1016ffff
- 从片选2处偏移0开始的地方被映射到地址范围0x30000000...0x10000000
或者,若是父节点和子节点的地址空间是相同的,那么节点也能够添加空的ranges属性。空的ranges属性意味着子节点地址空间中的地址被1:1地映射到父节点地址空间。
你可能会问为何地址转换老是用在全部1:1映射的状况下。一些总线(好比PCI)具备彻底不一样的地址空间,而这些细节必须暴露给操做系统。其余总线具备DMA引擎,这些引擎须要知道总线上的实际滴孩子。有时候设备须要被组合在一块儿,由于它们都共享有一样的软件可编程物理地址映射方法。该不应用1:1映射更大程度上取决于操做系统所需的信息以及硬件设计。
你应该也注意到,i2c@1,0节点中没有ranges属性,缘由是不像外部总线,i2c总线上的设备没有内存映射到CPU的地址域。相反地,CPU经过i2c@1,0设备间接地访问rtc@58设备。缺乏ranges属性意味着,设备不能直接被除了其父节点以外的任何设备访问
中断是怎样工做的
________________________________________________
中断不一样于遵循树天然结构的地址范围转换,中断信号可能来自以及终止在机器的任何设备。不像设备树中天然表示的设备寻址,中断信号是以独立于设备树的节点之间的连接表示的。四个属性用来描述中断的联系:
- interrupt-controller —— 一个空的属性声明接收中断信号的设备为节点
- #interrupt-cells —— 这是中断控制器节点的属性。它代表这个中断控制器的中断描述符符中有多少单元。(相似于#adderss-cells以及#size-cells)
- interrupt-parent —— 包含phandle的设备节点的一个属性,这个phandle指向它所链接到的中断控制器
- interrupts —— 包含中断描述符列表的设备节点的一个属性,每个设备上的中断输出信号都有一个
中断描述符是一个或多个单元的数据(由#interrupt-cells指定),它们指定设备链接到哪一个中断输入。大部分设备只有单个中断输出,以下面的例子所示,不过设备上也有可能有多个中断输出。中断描述符的意义彻底取决于中断控制器设备的绑定。每个中断控制器能够决定须要多少个单元才能惟一地定义一个中断输入。
下面的代码添加了链接到咱们的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属性来制定一个不一样的中断输入信号。
- #interrupt-cells是2,所以每个中断描述符有2个单元。这个例子采用了使用了常见的模式,用第一个单元来编码中断线号,用第二个单元来编码标志,好比高有效仍是低有效又或者是边缘触发仍是电平触发。对于任何给定的中断控制器,请参考控制器绑定文档来了解描述符是怎样编码的。
设备特定数据
除了公共的属性以外,咱们能够添加任何属性以及子节点到节点。咱们能够添加操做系统须要的任何数据,只要遵照一些规则便可。
首先,新的设备特定的属性名字应该使用生产厂商的前缀从而它们不会与现存的标准属性名称冲突。
其次,属性以及子节点的意义必须以绑定的形式记录下来从而设备驱动做者了解怎样翻译数据。绑定记录一个特定的compatible值意味着什么,它应该要有什么属性,它可能会有什么样的子节点以及设备表示什么。每个惟一的compatible值应该有它本身的绑定(或者声明与其余compatible值得兼容性)。新设备的绑定在这个wiki中被记录。请看主页描述文档格和审查过程。
第三,请将新的绑定提交到devicetree-discuss@lists.ozlabs.org邮件列表进行审查。审查新的绑定会抓住许多未来可能致使问题常见错误。
特殊节点
________________________________________________
aliases节点
一个特定的节点一般是以完整的路径来引用,好比/external-bus/ethernet@0,0,不过当一个用户真的想知道“哪一个设备是eth0”时,这将会很繁琐。aliases节点能够用来为一个完整的设备路径分配一个短的别名。好比:
aliases
{
ethernet0
=
&
eth0
;
serial0
=
&
serial0
;
};
当须要为设备指定一个标示符时,操做系统欢迎你们使用别名。
你将会注意到这里使用了一个新的语法。propert = &label; 语法分配由标签引用并做为字符串属性的的完整节点路径。这与早前使用的phandle = <&label>;不一样,它在单元中嵌入了phandle值。
chosen节点
chosen节点并不表明真实的设备,不过充当为在固件和操做系统之间传递数据的地方,好比启动参数。在chosen节点中的数据不表明硬件。典型状况下,chosen节点在.dts源文件中留空并在启动时填充。
在咱们的系统中,固件可能会添加下面的内容到chosen节点:
chosen
{
bootargs
=
"root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200"
;
};
高级主题
________________________________________________
高级的样例机器
如今咱们已经理解了基本定义,让咱们添加一些硬件到样例机器中来讨论一些更复杂的使用案例。
高级的样例机器添加了一个PCI主桥配有内存映射到0x10180000的控制寄存器和编程至从0x80000000地址以上开始的BARs。
假设咱们已经知道关于设备树以上内容,咱们能够从添加以下节点描述PCI主桥开始。
pci@10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
};