设备树参考文档(Device Tree Usage)
文章目录
0 官方文档
https://elinux.org/Device_Tree_Reference 官方手册
https://elinux.org/Device_Tree_Usage 入门示例(本文主要从此处翻译)
1 基本结构
设备树是一个树形结构,由节点和相关的属性组成。属性是键值对(key-val)结构;节点既包含属性,也包含子节点,举例来说,一个简单的.dts
结构:
/dts-v1/;
/ {
node1 {
a-string-property = "A string";
a-string-list-property = "first string", "second string";
// hex is implied in byte arrays. no '0x' prefix is required
a-byte-data-property = [01 23 34 56];
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
和node2
- 一组
node1
的子节点:child-node1
和child-node2
- 分布在树节点上的一些属性
属性是简单的键值对,其中的值可以为空,也可以包含任意字节流。有一些基本的数据类型可以在设备树源文件中表示。
- 字符串 string,以null结尾,由双引号包裹
string-property = "a string";
- 元组 cell,包含32位无符号整型数,由尖括号包裹
cell-property = <0xbeef 123 0xabcd1234>;
- 二进制数据 Binary,由方括号包裹
binary-property = [0x01 0x23 0x45 0x67];
- 逗号连接不同类型数据
mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
- 逗号也可以用于连接不同类型的字符串
string-list = "red fish", "blue fish";
2 基本概念
本节假设有一个简单的机器,我们通过建立设备树文件来描述它。
2.1 从一个具体的机器开始
考虑以下假想的机器(大致基于ARM Versatile),由“Acme”制造,命名为“Coyote’s Revenge”:
- 一个32位ARM CPU
- 处理器本地总线连接到内存映射的串行端口、spi总线控制器、i2c控制器、中断控制器、和外部总线桥
- 1个256MB的SDRAM,基地址为0
- 2个地址分别为0x101F1000和0x101F2000的串口
- 1个GPIO控制器,地址为0x101F3000
- 1个地址为0x10170000的SPI控制器,包含以下设备
- 带有SS引脚的MMC插槽,连接到GPIO #1
- 外部总线桥,带有下列设备
- SMC SMC91111以太网设备,连接到地址为0x10100000的外部总线
- 地址为0x10160000的i2c控制器,包含以下设备
- Maxim DS1338实时时钟,对应从属地址1101000 (0x58)
- 64MB的NOR flash,地址为0x30000000
2.2 初始结构
第一步是为机器设计一个骨架结构。这是有效设备树所需的最小结构。在这个阶段,希望唯一地标识机器。
/dts-v1/;
/ {
compatible = "acme,coyotes-revenge";
};
compatible
指定系统的名称。它包含一个“<manufacturer>,<model>
”形式的字符串。重要的是要指定确切的设备,并包括制造商名称,以避免名称空间冲突。由于操作系统将使用compatible值来决定如何在机器上运行,因此将正确的数据放入该属性非常重要。
理论上,compatible
是操作系统唯一标识一台机器所需的所有数据。如果所有的机器细节都是硬编码的,那么操作系统可以在顶层com中专门查找“acme,coyotes-revenge”。
2.3 CPUs
下一步是描述每一个CPU。这里添加一个命名为cpus的节点,其包含两个cpu子节点。在本例中,系统是ARM的双核Cortex A9系统。
/dts-v1/;
/ {
compatible = "acme,coyotes-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
};
cpu@1 {
compatible = "arm,cortex-a9";
};
};
};
同样可以用“<manufacturer>,<model>
”的形式指定确切的cpu型号。
稍后将向cpu节点添加更多属性,但我们首先需要讨论更多基本概念。
2.4 节点名
每个节点必须具有<name> [@<unit-address>]
格式的名称。
<name>
是一个简单的 ascii 字符串,最长可达31个字符。一般来说,节点是根据它所代表的设备类型来命名的。也就是说。3com以太网适配器的节点应该使用ethernet
,而不是3com509
。
如果节点描述具有地址的设备,则包含@<unit-address>
。通常,单元地址是用于访问设备的主地址,并在节点的reg
属性中列出。稍后我们将在本文档中介绍reg
属性。
兄弟节点(即同一级节点下的不同节点)必须是唯一命名的,但是对于多个节点来说,只要地址不同,使用相同的通用名称是正常的(例如,series@101f1000 & series@101f2000)。
有关节点命名的详细信息,请参阅 ePAPR 规范的2.2.1节。
2.5 设备
系统中的每个设备都由一个设备树节点表示。下一步是用每个设备的节点填充树。在我们讨论如何处理地址范围和irqs之前,新节点将保持空状态。
/dts-v1/;
/ {
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";
};
};
};
在此树中,为系统中的每个设备添加了一个节点,层次结构反映了设备的连接情况。
例中,外部总线上的设备是外部总线节点external-bus的子节点,i2c设备是i2c总线控制节点的子节点。一般来说,层次结构从CPU的角度展示系统视图,即CPU是如何看待这个节点挂在那个总线/设备下的。
这个树在这一点上是无效的。它缺少关于设备之间连接的信息。这些数据将在稍后添加。
注意:
- 每个设备节点都有一个
compatible
属性。 - flash节点在
compatible
属性中有两个字符串。请继续阅读下一节以了解原因。 - 如前所述,节点名称反映设备的类型,而不是特定的型号。请参阅ePAPR规范的第2.2.2节,应尽可能使用已定义的通用节点名称。
2.6 理解compatible
属性
树中表示设备的每个节点都必须具有compatible
属性,其用于决定将哪个设备驱动程序绑定到具体设备。
compatible
是一个字符串列表。列表中的第一个字符串以<manufacturer>,<model>
的形式指定节点所表示的确切设备。后续字符串表示该设备兼容的其他设备。
例如,Freescale MPC8349 SoC有一个串行器件,实现了National Semiconductor ns16550寄存器接口。因此,MPC8349串行设备的兼容属性应该是:compatible = "fsl,mpc8349-uart","ns16550"
。在这种情况下,fsl,mpc8349-uart
指定了确切的设备,ns16550
表示它与National Semiconductor 16550 UART兼容。
注意:ns16550没有制造商前缀纯粹是由于历史原因。所有新的兼容值都应该使用制造商前缀。
这种做法允许将现有的设备驱动程序绑定到较新的设备,同时仍然唯一地标识确切的硬件。
警告:不要使用通配兼容的符号,如“fsl,mpc83xx-uart
”或类似的值。硅供应商总是会做出改变,打破你的通配符假设,而这时改变已经太晚了。相反,选择一个特定的芯片实现,并使所有后续芯片与之兼容。
3 地址
可寻址的设备使用以下属性将地址信息编码到设备树中:
- reg
- #address-cells
- #size-cells
每个可寻址设备有一个reg
,这是一个tuple列表,形式为reg = <address1 length1 [address2 length2] [address3 length3] … >
。每个address
和length
为一对tuple,表示设备使用的地址范围。每个address
是一个或多个32位整数的列表,表示为一个或多个cell。同样,length
可以是一个或多个cell,也可以为空。
由于address
和length
字段都是可变大小的变量,因此使用父节点中的#address-cells
和#size-cells
属性来声明每个字段中有多少个单元格。或者换句话说,正确解释reg
属性需要父节点的#address-cells
和#size-cells
值。为了了解这一切是如何工作的,让我们将寻址属性添加到示例设备树中,从cpu开始。
3.1 CPU地址
在讨论寻址时,CPU节点代表了最简单的情况。每个CPU被分配一个唯一的ID,并且size与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
值是一个单独的uint32。
在本例中,两个cpu分别分配了地址0和1。对于CPU节点,#size-cells为0,因为每个CPU只分配地址不分配地址范围。
注意到 reg
值与节点名称中的值相匹配。按照约定,如果节点具有 reg
属性,则节点名必须包含 unit-address
,即 reg
属性中的第一个 address
值。
3.2 内存映射设备
不同于在cpu节点中讨论的单个地址值,内存映射设备被分配了一个地址范围,它关联这些地址。
#size-cells
用于声明每个reg
元组中的长度字段有多大。在下面的示例中,每个address是1个cell,每个length也是1个cell,这在32位系统中是非常普遍的。64位机器可以将#address-cells
和#size-cells
的值设置为2来在设备树中获得64位寻址(1个cell是32位,32+32获得64位寻址空间)。
/dts-v1/;
/ {
#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设备地址被分配了两个地址范围;0 x101f3000…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>;
};
};
external-bus
使用2个cell作为地址值;一个用于芯片编号的选择,另一个用于芯片偏移量的选择。length字段为1个cell,因为只有地址的偏移部分需要有一个范围。因此,在本例中,每个reg
包含3个cell,芯片编号,偏移量和地址长度。
由于地址域包含在节点及其子节点中,因此父节点可以自由地定义寻址方案(需要对总线有意义)。直接的父子节点之外的节点通常不必关心寻址域,并且必须将地址从一个域映射到另一个域。
注意:这里并没有展示cpu是如何将0 0或1 0这种地址编号+偏移的映射转换为cpu可以理解的地址空间,后续会讲到。
3.3 非内存映射设备
其他设备在处理器总线上没有内存映射。它们可以有地址范围,但是它们不能被CPU直接访问。相反,父设备的驱动程序将执行间接访问。
以i2c设备为例,rtc设备都分配了一个地址,但没有与之关联的长度或范围。这看起来与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>;
};
};
3.4 ranges 地址转换
我们已经讨论了如何为设备分配地址,但是在这一点上,这些地址只是设备节点的本地地址。它还没有描述如何从这些地址映射到 CPU 可以使用的地址。
根节点总是描述 CPU 对地址空间的看法。根节点的子节点已经在使用 CPU 的地址域,因此不需要任何显式映射。例如,serial@101f0000设备被直接分配地址0x101f0000。
非根的直接子节点不使用 CPU 的地址域。为了获得内存映射地址,设备树必须指定如何将地址从一个域转换到另一个域。ranges
属性用于此目的。
下面是添加了 ranges
属性的示例设备树。
/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";
#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
表中的每个条目都是一个tuple,包含子地址、父地址和子地址空间中区域的大小。每个字段的大小由子字段的 #address-cells
值、父字段的 #address-cells
值和子字段的 #size-cells
值确定。对于我们示例中的外部总线,子地址是2个cell,父地址是1个cell,size也是1个cell。目前正在转换三个范围:
- 芯片0偏移0映射到地址范围0x10100000…0x1010ffff
- 芯片1偏移0映射到地址范围0x10160000…0x1016ffff
- 芯片2偏移0映射到地址范围0x30000000…0x30ffffff
另外,如果父地址空间和子地址空间相同,则节点可以添加一个空的ranges属性。空range属性的存在意味着子地址空间中的地址以1:1的比例映射到父地址空间。
您可能会问,既然地址转换都可以用1:1映射编写,为什么还要使用地址转换呢。有些总线(如 PCI)具有完全不同的地址空间,其详细信息需要向操作系统公开。另一些有DMA引擎,需要知道真正的总线上的地址。有时设备需要组合在一起,因为它们共享相同的物理地址映射。是否应该使用1:1映射在很大程度上取决于操作系统所需的信息以及硬件设计。
您还应该注意到i2c@1,0节点中没有ranges
属性。这样做的原因是,与外部总线不同,i2c总线上的设备没有内存映射到CPU的地址域。相反,CPU通过i2c@1,0设备间接访问rtc@58设备。缺少ranges
属性意味着一个设备不能被它的父设备以外的任何设备直接访问。
4 中断
与遵循树的自然结构的地址范围转换不同,中断信号可以起源于和终止于机器中的任何设备。与设备树中自然表达的设备寻址不同,中断信号被表示为独立于树的节点之间的链接。四个属性用于描述中断连接:
interrupt-controller
:空属性,声明一个节点是接收中断信号的设备#interrupt-cells
:中断控制器节点的属性。它声明这个中断控制器的中断标识符中有多少个cell(类似于#address-cells
和#size-cells
)。interrupt-parent
:设备节点的属性,它包含一个连接到中断控制器的句柄。没有interrupt-parent
属性的节点也可以从它们的父节点继承该属性。interrupts
:设备节点的属性,包含中断标识符(interrupt specifier)的列表,设备上的每个中断输出信号对应一个中断标识符。
interrupt specifier是一个或多个cell组成的数据(由#interrupt-cells
指定),它指定设备连接到哪个中断输入。大多数设备只有一个中断输出,如下面的例子所示。一个设备上有多个中断输出也是可行的。interrupt specifier的含义完全取决于中断控制器设备的绑定。每个中断控制器可以决定需要多少个cell来唯一地定义一个中断输入。
下面的代码将中断连接添加到 Coyote Revenge:
/dts-v1/;
/ {
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-cells为2,所以每个中断标识符有2个单元格。这个例子使用的常见模版是,使用第一个cell表示中断行号,使用第二个cell表示flag,如高/低电平,或是边缘/水平触发等。对于任何给定的中断控制器,请参考控制器的文档来了解是如何编码的。
5 其他特定数据
除了公共属性之外,还可以向节点添加任意属性和子节点。只要遵循某些规则,就可以添加操作系统所需的任何数据。
首先,新的特定于设备的属性名应该使用制造商前缀,这样它们就不会与现有的标准属性名冲突。
其次,必须在绑定的文档中记录属性和子节点的含义,以便设备驱动程序开发者知道如何解释数据。文档记录了特定兼容值的含义、它应该具有的属性、它可能具有的子节点以及它表示的设备。每个唯一compatible
值都应该有自己的绑定(或声明与另一个兼容值兼容)。有关文档格式和评审过程的描述,请参见主页。
第三,在 devicetree-talk@lists.ozlabs.org 邮件列表上发布新的绑定以供审查。检查新的绑定会发现许多常见的错误,这些错误将在未来引起问题。
6 特殊节点
6.1 aliases
节点
特定节点通常由完整路径引用,如/external-bus/ethernet@0,0,但是当用户真正想知道的是“哪个设备是eth0?”时,这会变得很麻烦。aliases
节点可用于为完整设备路径分配短别名。例如:
aliases {
ethernet0 = ð0;
serial0 = &serial0;
};
注意到这里使用了一种新的语法。property = &label
; 语法将label引用的完整节点路径分配为字符串属性。这不同于前面使用的 phandle = <&label>
,后者将一个 phandle 值插入到cell中。
6.2 chosen
节点
所选节点并不代表真正的设备,而是作为在固件和操作系统之间传递数据的地方,比如引导参数。所选节点中的数据不代表硬件。通常,所选节点在.dts源文件中为空,并在引导时填充。
在我们的示例系统中,固件可能会向所选节点添加以下内容:
chosen {
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};
7 高级主题
7.1 高级样机
现在我们已经定义了基础,让我们向示例机器添加一些硬件,以讨论一些更复杂的用例。
下面的机器添加了一个PCI host bridge,其中控制寄存器内存映射到0x10180000,并将BARs被规划到地址0x80000000上启动。
鉴于我们已经了解了设备树,我们可以从添加以下节点开始,以描述PCI主机桥接器。
pci@10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
};
7.2 PCI Host Bridge
介绍Host/PCI桥接节点。
注意,本节假定有一些PCI的基本知识。这不是一个关于PCI的教程,如果你需要更深入的信息,请阅读[1]。您也可以参考ePAPR v1.1或PCI总线绑定到开放固件。
7.2.1 PCI总线编号
每个 PCI 总线段都是唯一编号的,并且通过使用bus-range
属性(包含两个cell)在 PCI 节点中公开总线编号。第一个cell给出分配给这个节点的总线号,第二个cell给出从属 PCI 总线的最大总线号。
样本机只有一个pci总线,因此两个单元格都为0。
pci@0x10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
bus-range = <0 0>;
};
7.2.2 PCI地址转换
与前面描述的本地总线类似,PCI地址空间与CPU地址空间完全分离,因此需要将地址从PCI地址转换为CPU地址。与往常一样,这是通过ranges
, #address-cells
和#size-cells
属性完成的。
pci@0x10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
bus-range = <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>;
};
这里,子地址使用3个cell(前三个cell),PCI范围使用2个cell(后两个cell)。
第一个问题可能是,为什么我们需要三个32位cell来指定PCI地址。这三个cell被标记为 phys.hi, phys.mid 和 phys.low[2]。
phys.hi cell: npt000ss bbbbbbbb dddddfff rrrrrrrr
phys.mid cell: hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh
phys.low cell: llllllll llllllll llllllll llllllll
PCI 地址为64位宽,并被编码为 phy.mid 和 phy.low。然而,真正有趣的事情发生在 phy.high 中,它是一个 bit 字段:
n
: 可重定位的区域标志(在这里不起作用)p
: 预取(缓存)区域标志t
: 别名地址标志(在这里不起作用)ss
:空间类型00
: 配置空间01
: I/O空间10
: 32位内存空间11
: 64位内存空间
bbbbbbbb
:PCI 总线编号。PCI 可能是分层结构的。因此,我们可能会有 PCI/PCI 桥接器来定义子总线。ddddd
: 设备号,通常与 IDSEL 信号连接相关联。fff
: 函数号。用于多功能 PCI 设备。rrrrrrrr
: 寄存器编号; 用于配置周期。
在PCI地址转换中,重要的字段是p
和ss
。p
和ss
在phys.hi中的值确定正在访问哪个PCI地址空间。看一下ranges
属性,我们有三个区域:
- 一个32位的可预取内存区域,从PCI地址0x80000000开始,大小为512 MByte,将映射到主机CPU上的地址0x80000000。
- 一个32位不可预取的内存区域,从PCI地址0xa0000000开始,256 MByte大小,将映射到主机CPU上的地址0xa0000000。
- 一个从PCI地址0x00000000开始的16 MByte大小的I/O区域,它将映射到主机CPU上的地址0xb0000000。
phys.hi字段的存在意味着操作系统需要知道该节点代表PCI桥,这样它就可以忽略不相关的字段进行翻译。操作系统将在pci总线节点中查找字符串“pci”,以确定是否需要屏蔽额外字段。
7.2.3 PCI DMA地址翻译
上述范围定义了 CPU 如何看待 PCI 内存,并帮助 CPU 设置正确的内存窗口和写入正确的参数到各种 PCI 设备寄存器。这有时称为出站内存(outbound memory)。
地址转换的一个特殊情况涉及到 PCI 主机硬件如何查看系统的核心内存。当 PCI 主机控制器充当主机并独立访问系统的核心内存时,就会发生这种情况。由于这通常与 CPU 的视图不同(由于内存线路的连接方式) ,这可能需要在初始化时编程到 PCI 主机控制器中。这被看作是一种 DMA,因为 PCI 总线独立执行直接内存访问,因此映射被命名为 dma-range。这种类型的内存映射有时称为入站内存(inbound memory),不属于 PCI 设备树规范的一部分。
在某些情况下,ROM (BIOS)或类似的设备将在引导时设置这些寄存器,但在其他情况下,PCI 控制器完全未初始化,需要从设备树设置这些转换。然后,PCI 主机驱动程序通常会解析 dma-range 属性,并相应地在主机控制器中设置一些寄存器。
扩展上面的例子:
pci@0x10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
bus-range = <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
dma-ranges = <0x02000000 0 0x00000000 0x80000000 0 0x20000000>;
};
这个dma-ranges示例表明,从PCI主机控制器的角度来看,位于PCI地址0x00000000的512 MB将出现在位于地址0x80000000的主核心内存中。正如您所看到的,我们只是将ss
地址类型设置为0x02,表明这是一些32位内存。
7.3 高级中断映射
现在我们来到最有趣的部分,PCI中断映射。PCI设备可以使用#INTA、#INTB、#INTC和#INTD线触发中断。中断名称前面的# hash符号表示它处于低电平活动状态,这是一种常见的惯例,PCI中断线总是处于低电平活动状态。单一功能的设备必须使用#INTA进行中断。多功能设备如果使用单个中断引脚,则必须使用#INTA,如果使用两个中断引脚,则必须使用#INTA和#INTB,等等。由于这些规则,相比于#INTB、#INTC和#INTD,#INTA通常会被更多的函数使用。为了通过将负载分配到#INTA到#INTD的四条IRQ线上,每个PCI插槽或设备通常以轮转(原文是rotating)的方式连接到中断控制器上的不同输入,以避免所有#INTA客户端连接到同一条传入中断线上。这个过程被称为对中断打乱(swizzling)。因此,设备树需要一种将每个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-range = <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,不像系统中断控制器使用2个cell那样一个是irq编号,另一个是flag。PCI只需要一个cell用于中断,因为PCI中断被指定为始终是低电平敏感的。
在我们的示例中,分别有2个PCI插槽和4条中断线,因此我们必须将8条中断线映射到中断控制器。这是使用interrupt-map属性完成的。中断映射的具体过程在[3]中有描述。
因为中断号(#INTA等)不足以区分单个PCI总线上的几个PCI设备,还必须表示哪个PCI设备触发了中断线。幸运的是,每个PCI设备都有一个唯一的设备号,我们可以使用它。为了区分多个PCI设备的中断,我们需要一个由PCI设备号和PCI中断号组成的元组tuple。更一般地说,我们构造一个有四个cell的unit中断标识符:
- 三个#address-cells,包括phys.hi, phys.mid, phys.low
- 一个##interrupt-cell,为(#INTA, #INTB, #INTC, #INTD)之一
因为我们只需要PCI地址的设备号部分,interrupt-map-mask
属性开始发挥作用。interrupt-map-mask
也是一个4元组,类似于unit中断标识符。掩码中的第一个cell表示应该考虑unit中断标识符的哪一部分。在例子中,可以看到只有设备编号部分的phys.hi是必需的,我们需要3位来区分四条中断线(计数PCI中断线从1开始,而不是从0开始!)。
现在我们可以构造interrupt-map
属性。该属性的每个行由子PCI总线的unit中断标识符(占用4cell),父句柄(负责服务中断的中断控制器,占用1cell)和父unit中断标识符(占2cell)组成。因此,在第一行中,我们可以读到PCI中断#INTA被映射到中断控制器的低电平触水平触发的irq9上[4]。
目前唯一缺少的部分是PCI总线unit中断标识符中的奇怪数字。uint中断标识符的重要部分是来自phys.hi的bit字段。设备号是板特定的,它取决于每个PCI主机控制器如何激活每个设备上的IDSEL引脚。在此例中,为PCI slot 1分配设备id 24 (0x18),为PCI slot 2分配设备id 25 (0x19)。每个slotphys.hi通过将设备号左移11位到bit位的ddddd部分来确定,如下所示:
- 0x18左移11位为0xC000
- 0x19左移11位为0xC800
把所有这些放在一起,interrupt-map属性表示:
- slot 1的 #INTA 是 IRQ9,主中断控制器上的低电平水平触发
- slot 1的 #INTB 是 IRQ10,主中断控制器上的低电平水平触发
- slot 1的 #INTC 是 IRQ11,主中断控制器上的低电平水平触发
- slot 1的 #INTD 是 IRQ12,主中断控制器上的低电平水平触发
以及
- slot 2的 #INTA 是 IRQ10,主中断控制器上的低电平水平触发
- slot 2的 #INTB 是 IRQ11,主中断控制器上的低电平水平触发
- slot 2的 #INTC 是 IRQ12,主中断控制器上的低电平水平触发
- slot 2的 #INTD 是 IRQ9,主中断控制器上的低电平水平触发
interrupts = <8 0>;
属性描述了host/PCI-bridge控制器本身可能触发的中断。不要将这些中断与可能触发的中断PCI device混淆(使用 INTA、 INTB、 …)。
最后要注意的一件事。就像interrupt-parent
属性一样,节点上interrupt-map
属性的存在将改变所有子节点和孙子节点的默认中断控制器。在这个PCI示例中,这意味着PCI主机桥接器成为默认的中断控制器。如果通过PCI总线连接的设备与另一个中断控制器有直接连接,那么它还需要指定自己的interrupt-parent
属性。