PCIe 设备树详细讲解
Posted smartvxworks
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PCIe 设备树详细讲解相关的知识,希望对你有一定的参考价值。
目录
1.基本数据格式
设备树是节点和属性的简单树结构。属性是键值对,节点可以同时包含属性和子节点。例如,下面是一个 .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
" and "node2
" node1
的几个子节点: "child-node1
" and "child-node2
"- 一堆属性散落在树上。
属性是简单的键值对,其中的值可以为空或包含任意字节流。虽然数据类型未编码到数据结构中,但有一些基本数据表示形式可以在设备树源文件中表示。
- 文本字符串(空终止)用双引号表示:
string-property = "a string";
- “单元格”是用尖括号分隔的 32 位无符号整数:
cell-property = <0xbeef 123 0xabcd1234>;
- 二进制数据用方括号分隔:
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多功能),由“Acme”制造,并命名为“Coyote's Revenge”:
- 一个 32 位 ARM CPU
- 处理器本地总线连接到内存映射串行端口、spi 总线控制器、i2c 控制器、中断控制器和外部总线桥接器
- 基于 0 的 256MB SDRAM
- 2 个基于0x101F1000和0x101F2000的串行端口
- 基于0x101F3000的 GPIO 控制器
- 基于0x10170000的 SPI 控制器,具有以下器件
- 带有连接到 GPIO #1 的 SS 引脚的 MMC 插槽
- 具有以下设备的外部总线桥接器
- SMC SMC91111 以太网设备连接到基于0x10100000的外部总线
- 基于0x10160000的 i2c 控制器,具有以下设备
- Maxim DS1338实时时钟响应从属地址 1101000 (0x58)
- 基于0x30000000的64MB NOR闪存
2.2初始结构
第一步是为板卡铺设dts框架结构。这是有效设备树所需的最基本结构。在此阶段,您希望唯一标识您的硬件板卡。
/dts-v1/;
/
compatible = "acme,coyotes-revenge";
;
compatible
指定系统的名称。它包含一个字符串,格式为“<制造商>,<模型>。请务必指定确切的设备,并包含制造商名称以避免命名空间冲突。由于操作系统将使用该值来决定如何在您的硬件板卡上运行,因此将正确的数据放入此属性中非常重要。
从理论上讲,兼容的是操作系统唯一标识硬件板卡所需的所有数据。如果所有硬件板卡细节都是硬编码的,那么操作系统可以在顶级属性中专门查找“acme,coyotes-revenge”,即即通过顶层属性"acme,coyotes-revenge"来标识您唯一的硬件板卡设备。compatible
2.3处理器
下一步是描述每个 CPU。将为每个 CPU 添加一个名为“cpus”的容器节点,并附带一个子节点。在这种情况下,CPU是来自ARM的双核A9。
/dts-v1/;
/
compatible = "acme,coyotes-revenge";
cpus
cpu@0
compatible = "arm,cortex-a9";
;
cpu@1
compatible = "arm,cortex-a9";
;
;
;
每个 cpu 节点中的兼容属性是一个字符串,它以形式指定确切的 cpu 模型,就像顶层的兼容属性一样。<manufacturer>,<model>
稍后会向 cpu 节点添加更多属性,但我们首先需要讨论更多基本概念。
2.4节点名称
值得花点时间讨论一下命名约定。每个节点必须具有格式的名称 <name>[@<unit-address>
]。
<name>
是一个简单的 ascii 字符串,长度最多为 31 个字符。通常,节点是根据它所代表的设备类型来命名的。例如3com公司的 以太网适配器的节点将使用名称ethernet ,而不是 3com509
如果节点描述具有地址的设备,则包括设备地址。通常,设备地址是用于访问设备的主地址,并在节点的属性中列出。我们将在本文档的后面部分介绍 reg 属性。reg
同级节点必须具有唯一名称,但只要地址不同(即serial@101f1000 &serial@101f2000),多个节点使用相同的通用名称是正常的。
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 的角度表示系统的视图。
此树此时无效。它缺少有关设备之间连接的信息。该数据的稍后将添加。
此树中需要注意的一些事项:
- 每个设备节点都有一个属性。
compatible
- 闪存节点在兼容属性中有 2 个字符串。请继续阅读下一节以了解原因。
- 如前所述,节点名称反映的是设备类型,而不是特定型号。
2.6了解兼容
属性
树中表示设备的每个节点都必须具有该属性。 是操作系统用于决定要绑定到设备的设备驱动程序的密钥。compatible
compatible
是字符串的列表。列表中的第一个字符串指定节点以形式表示的确切设备。以下字符串表示与设备兼容的其他设备。"<manufacturer>,<model>"
例如,飞思卡尔MPC8349片上系统(SoC)具有一个串行器件,该器件实现了美国国家半导体公司ns16550寄存器接口。因此,MPC8349串行器件的兼容属性应为:compatible = "fsl,mpc8349-uart", "ns16550"。在这种情况下,指定确切的设备,并指出它与美国国家半导体16550 UART的寄存器级兼容,即不通厂家的控制器,寄存器一样,可以兼容,驱动也一样,只需要修改一下名称compatible即可匹配初始化。
注意:没有制造商前缀,纯粹出于历史原因。所有新的兼容值都应使用制造商前缀。ns16550
这种做法允许将现有设备驱动程序绑定到较新的设备,同时仍然唯一地标识确切的硬件。
警告:使用确切的控制器名称,请勿使用通配符兼容的值,如“fsl,mpc83xx-uart”或类似值。芯片供应商总是会做出改变,打破你的通配符假设,因为改变它为时已晚。相反,选择特定的芯片实现,并使所有后续芯片与其兼容。
3.寻址的工作原理
可寻址设备使用以下属性将地址信息编码到设备树中:
reg
#address-cells
#size-cells
每个可寻址设备都会获得一个元组列表,其形式为reg = <address1 length1 [address2 length2] [address3 length3] ... > 。每个元组表示设备使用的地址范围。每个地址值都是一个或多个称为单元格的 32 位整数的列表。同样,长度值可以是单元格列表,也可以是空的。
由于地址和长度字段都是可变大小的变量,因此父节点中的 和 属性用于说明每个字段中有多少个单元格。换句话说,正确解释 reg 属性需要父节点的#address单元格和#size单元格值。若要了解这一切的工作原理,让我们将寻址属性添加到示例设备树中,从 CPU 开始。#address-cells
#size-cells
3.1中央处理器寻址
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>;
;
;
在 节点中, 设置#address-cells为 1,并设置#size-cells为 0,这意味着cpu子节点reg = <0> 是单个 uint32,没有大小字段的地址。在这种情况下,将为两个 cpu 分配地址 reg = <0> 和reg = <1>。 #address-cells is 1和#size-cells is 0 就是reg属性只有一个地址,没有长度属性。
您还会注意到该值与节点名称中的值匹配。按照惯例,如果节点具有属性,则节点名称cpu@0必须包含单位地址,cpu@0中的0即reg = <0>属性中的第一个地址值0。
3.2内存映射设备
内存映射设备不是像在 cpu 节点中找到的单个地址值那样,而是分配了它将响应的地址范围。 用于声明每个子元组中的长度字段有多大。在以下示例中,每个地址值为 1 个单元格(32 位),每个长度值也是 1 个单元格,这在 32 位系统上很常见。64 位计算机可能将值 2 用于#address单元,#size单元在设备树中获取 64 位寻址。#size-cells
reg
/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 >;
;
...
;
#address-cells = <1>; 和#size-cells = <1>;即声明了子节点serial@101f0000、serial@101f2000、gpio@101f3000等的reg属性只有2个值,第一个代表地址,第二个代表size
每个设备都分配有一个基址,以及它所分配的区域的大小。此示例中的 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>;
;
;
#address-cells = <2>;使用2个单元格作为地址值;一个用于芯片选择编号,一个用于从芯片基底偏移选择。长度字段仍保留为单个单元格,因为只有地址的偏移部分需要具有范围。因此,在此示例中,每个子节点属性 reg包含3个单元格,即芯片选择、偏移量和长度。
由于地址域包含在节点及其子节点中,因此父节点可以自由地定义对总线有意义的任何寻址方案。直接父节点和子节点之外的节点通常不必关心本地寻址域,并且必须映射地址才能从一个域到达另一个域。
3.3非内存映射设备
其他设备未在处理器总线上映射内存。它们可以具有地址范围,但 CPU 无法直接访问它们。相反,父设备的驱动程序将代表 CPU 执行间接访问。
以i2c设备为例,每个设备都分配有一个地址,但没有与之关联的长度或范围。这看起来与 CPU 地址分配大致相同。rtc@58即挂接在I2C总线下的一个RTC设备,即I2C从设备,其设备ID为58
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范围(地址转换)
我们已经讨论了如何将地址分配给设备,但此时这些地址仅是设备节点的本地地址。它尚未描述如何从这些地址映射到 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
是地址转换的列表。范围表中的每个条目都是一个元组,其中包含子地址、父地址和子地址空间中区域的大小。每个字段的大小是通过取子项的值、父项的值和子项的值来确定的。对于我们示例中的外部总线external-bus,#address-cells = <2>子地址为 2 个单元格,父地址#address-cells = <1>为 1 个单元格,大小#size-cells = <1>也是 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
三个ranges的转换如下:
- 芯片选择 0 的偏移量 0 映射到地址范围 0x10100000~0x1010ffff
- 芯片选择1的偏移量0映射到地址范围0x10160000~0x1016ffff
- 芯片选择 2 的偏移量 0 映射到地址范围 0x30000000~0x30ffffff
或者,如果父地址空间和子地址空间相同,则节点可以改为添加空属性。空范围属性的存在意味着子地址空间中的地址以 1:1 的比例映射到父地址空间。ranges
您可能会问,为什么使用地址转换,而地址转换都可以用 1:1 映射编写。某些总线(如 PCI)具有完全不同的地址空间,其详细信息需要向操作系统公开。其他人有DMA引擎,需要知道BUS总线上的真实地址。有时,设备需要分组在一起,因为它们都共享相同的软件可编程物理地址映射。是否应使用 1:1 映射在很大程度上取决于操作系统所需的信息和硬件设计。
您还应该注意到,i2c@1,0 节点中没有属性。这样做的原因是,与外部总线不同,i2c 总线上的设备不是在 CPU 的地址域上映射的内存。相反,CPU 通过 i2c@1,0 设备间接访问rtc@58设备。缺少属性意味着设备不能由其父设备以外的任何设备直接访问。ranges
ranges
4.中断的工作原理
与遵循设备树的自然结构的地址范围转换不同,中断信号可以源自和终止于机器中的任何设备。与在设备树中自然表示的设备寻址不同,中断信号表示为独立于树的节点之间的链接。有四个属性用于描述中断连接:
interrupt-controller
- 一个空属性,将节点声明为接收中断信号的设备#interrupt-cells
- 这是中断控制器节点的属性。它说明此中断控制器的中断说明符中有多少个单元 (Similar to#address-cells
and#size-cells
)。interrupt-parent
- 设备节点的属性,其中包含连接到它的中断控制器的 phandle。没有中断父属性的节点也可以从其父节点继承该属性。interrupts
- 设备节点的属性,包含中断说明符列表,每个中断指定符对应于设备上的每个中断输出信号。
中断说明符是一个或多个数据单元(由#interrupt单元指定),用于指定设备连接到哪个中断输入。大多数设备只有一个中断输出,如下面的示例所示,但一个设备上可以有多个中断输出。中断说明符的含义完全取决于中断控制器设备的绑定。每个中断控制器可以决定需要多少个单元来唯一地定义中断输入。
以下代码将中断连接添加到我们的示例设备树:
/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>;
;
;
;
需要注意的一些事情:
- 硬件设备具有单个中断控制器,中断controller@10140000。
- 标签“intc:”已添加到中断控制器节点,并且该标签用于为根节点中的中断父属性分配一个 phandle。此中断父值将成为系统的缺省值,因为除非显式覆盖它,否则所有子节点都将继承它。
- 每个设备都使用中断属性来指定不同的中断输入线。
- intc:节点中#interrupt单元数为 2,因此每个中断说明符有 2 个单元。此示例使用常见的模式,即使用第一个单元对中断行号进行编码,并使用第二个单元对标志(如高电平有效与低电平有效或边缘与电平敏感)进行编码。对于任何给定的中断控制器。
解释:
1.intc:节点即系统中唯一的一个中断控制器;
2.设备树中的所有节点,用的interrupts属性,缺省interrupt-parent = <&intc>时,将默认将intc作为父中断控制器;
3.intc:节点中的#interrupt-cells = <2>即所有以其为中断父节点的node的interrupts 属性包含两个单元,第一个是中断编号,第二个是中断属性(如高电平有效与低电平有效或边缘与电平敏感)。
5.设备特定数据
除了公共属性之外,还可以将任意属性和子节点添加到节点中。只要遵循某些规则,就可以添加操作系统所需的任何数据。
首先,特定于设备的新属性名称应使用制造商前缀,以便它们不会与现有标准属性名称冲突。
其次,必须在绑定中记录属性和子节点的含义,以便设备驱动程序作者知道如何解释数据。绑定记录了特定兼容值的含义、它应该具有哪些属性、它可能具有哪些子节点以及它表示什么设备。每个唯一值都应具有自己的绑定(或声明与另一个兼容值的兼容性)。compatible
第三,在 devicetree-discuss@lists.ozlabs.org 邮件列表上发布新的绑定以供审核。查看新绑定会发现许多常见错误,这些错误将在将来导致问题。
6.特殊节点
6.1别名
节点
特定节点通常由完整路径引用,例如 ,但是当用户真正想知道的是“哪个设备是 eth0?该节点可用于将短别名分配给完整设备路径。例如:/external-bus/ethernet@0,0
aliases
aliases
ethernet0 = ð0;
serial0 = &serial0;
;
欢迎操作系统在为设备分配标识符时使用别名。
您会注意到此处使用的新语法。property = &label;
该语法将标签引用的完整节点路径分配为字符串属性。这与前面phandle = < &label >;
使用的形式不同,后者将 phandle 值插入单元格中。
6.2选择
节点
节点不代表真实设备,而是用作在固件和操作系统之间传递数据的位置,例如引导参数。所选节点中的数据不代表硬件。通常,所选节点在 .dts 源文件中留空,并在启动时填充。chosen
在我们的示例系统中,固件可能会将以下内容添加到所选节点:
chosen
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
;
7.PCIe内容
7.1高级硬件板卡示例
现在我们已经定义了基础知识,让我们向示例硬件板卡添加一些硬件,以讨论一些更复杂的用例。
高级硬件板卡示例增加了一个PCI Host控制器,其控制寄存器映射到0x10180000,并且BARs编程为在地址0x80000000上方启动。
鉴于我们对设备树的了解,我们可以从添加以下节点开始,以描述PCI Host控制器。
pci@10180000
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
;
7.2 PCI Host控制器
本节介绍PCI Host控制器节点。
请注意,本节假定您具备 PCI 的一些基本知识。这不是一个关于PCI的教程,如果您需要一些更深入的信息。您还可以参考 PCI 总线绑定到开放固件。飞思卡尔MPC5200的完整工作示例可在此处找到。
7.2.1总线编号
每个 PCI 总线段的编号都是唯一的,并且总线编号通过使用包含两个单元的属性在 pci 节点中公开。第一个单元给出分配给此节点的总线编号,第二个单元给出任何从属 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 地址空间与 CPU 地址空间完全分开,因此需要进行地址转换才能从 PCI 地址转换为 CPU 地址。与往常一样,这是使用范围和属性完成的。
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>;
;
#address-cells = <3>即ranges前三个为地址空间,举例0x42000000 0 0x80000000三个单元为地址空间;
#size-cells = <2>即0 0x20000000两个单元为长度属性;
如您所见,子地址(PCI 地址)使用 3 个单元格(#address-cells = <3>),并且 PCI 范围编码为 2 个单元格(#size-cells = <2>)。第一个问题可能是,为什么我们需要三个32位单元来指定PCI地址。这三个地址被标记为phys.hi
,phys.mid
和phys.low
。
phys.hi
,phys.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 位,并编码为phys.mid(高32位地址)和phys.low
(低32位地址)
。然而,真正有趣的事情是在phys.high,这是一个属性领域,具体每一位代表的属性如下:
n
:可重定位区域标志(此处不起作用)p
:可预取(可缓存)区域标志t
:别名地址标志(此处不起作用)ss
:空格码- 00:配置空间
- 01: I/O 空间
- 10:32 位内存空间
- 11:64 位内存空间
bbbbbbbb
:PCI 总线编号。PCI 可以分层构建。因此,我们可能有 PCI/PCI 桥接器来定义子总线。ddddd
:设备编号,通常与 IDSEL 信号连接相关联。fff
:函数编号。用于多功能 PCI 设备。rrrrrrrr
: 注册号;用于配置周期。
出于 PCI 地址转换的目的,重要字段为 phys.hi 中 p 和 ss 的值确定正在访问哪个 PCI 地址空间。因此,纵观我们的系列属性,我们有三个区域:
- 从 PCI 地址开始的 32 位可预取内存区域0x80000000大小为 512 MB,将映射到主机 CPU 上的地址0x80000000。
- 从 PCI 地址开始的 32 位不可预取内存区域0xa0000000大小为 256 MB,将映射到主机 CPU 上的地址0xa0000000。
- 从 PCI 地址开始的 I/O 区域0x00000000大小为 16 MB,该区域将映射到主机 CPU 上的地址0xb0000000。
为了给作品扳手,phys.hi的存在,意味着操作系统需要知道节点代表PCI桥,以便它可以忽略不相关的场以进行转换。操作系统将在 PCI 总线节点中查找字符串“pci”,以确定是否需要屏蔽额外的字段。
7.2.3地址转换
上述范围定义了CPU如何查看PCI内存,并帮助CPU设置正确的内存窗口并将正确的参数写入各种PCI设备寄存器。这有时称为出站内存(PCIe outbound)。
地址转换的一个特殊情况涉及 PCI 主机硬件如何查看系统的核心内存。当 PCI 主控制器将充当主控制器并独立访问系统的核心内存时,就会发生这种情况。由于这通常与CPU的观点不同(由于内存线的接线方式),因此可能需要在初始化时将其编程到PCI主机控制器中。这被视为一种 DMA,因为 PCI 总线独立执行直接内存访问,因此映射被命名为 dma 范围。这种类型的内存映射有时称为入站内存(PCIe inbound),并且不是 PCI 设备树规范的一部分。
在某些情况下,ROM (Bios)或类似设备将在启动时设置这些寄存器,但在其他情况下,PCI控制器完全未初始化,需要从设备树中设置这些转换。然后,PCI 主机驱动程序通常会分析 dma 范围属性,并相应地在主机控制器中设置一些寄存器。
扩展上面的示例:
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 范围条目表示,从 PCI 主控制器的角度来看,PCI 地址0x00000000处的 512 MB 将出现在主内核内存中地址0x80000000处。如您所见,我们只是将ss地址类型设置为0x02表示这是一些32位内存。
pcie inbound和outbound关系_smartvxworks的博客-CSDN博客
7.3高级中断映射
现在我们来到最有趣的部分,PCI中断映射。PCI 设备可以使用#INTA、#INTB、#INTC和#INTD的导线触发中断。中断名称前面的 # hash 符号表示它是低电平活动状态,这是一种常见的约定,而 PCI 中断线始终处于活动状态。单功能设备有义务使用#INTA进行中断。多功能设备必须使用#INTA(如果它使用单个中断引脚)、#INTA和#INTB(如果它使用两个中断引脚等)。由于这些规则,#INTA通常由比#INTB、#INTC和#INTD更多的函数使用。为了在通过#INTD支持#INTA的四条IRQ线路上的分配,负载均衡化,每个PCI插槽或设备通常以旋转方式连接到中断控制器上的不同输入,以避免所有#INTA客户端连接到同一条传入中断线路。此过程称为旋转中断。因此,设备树需要一种将每个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中断编号#interrupt-cells = <1>仅使用一个单元,这与使用2个单元的系统中断控制器不同;一个用于 irq 编号,一个用于标志。PCI 只需要一个单元进行中断,因为 PCI 中断被指定为始终对低电平敏感,即中断触发方式已经默认指定为低电平触发。
在我们的示例板中,我们有 2 个 PCI 插槽,分别有 4 条中断线,因此我们必须将 8 条中断线映射到中断控制器。这是使用中断映射属性完成的。
由于中断编号(#INTA等)不足以区分单个PCI总线上的多个PCI设备,因此我们还必须指出哪个PCI设备触发了中断线。幸运的是,每个PCI设备都有一个我们可以使用的唯一设备编号Device ID。为了区分多个 PCI 设备的中断,我们需要一个由 PCI 设备编号和 PCI 中断编号组成的元组。更一般地说,我们构造一个单元中断说明符,其中包含四个单元格:
例举:interrupt-map = <0xc000 0 0 1 &intc 9 3 // 1st slot
0xc000 0 0 2 &intc 10 3
RK3399平台开发系列讲解(PCI/PCI-E)5.52PCIE RC侧设备树及配置
RK3399平台开发系列讲解(PCI/PCI-E)5.53PCIE RC侧 地址映射
RK3399平台开发系列讲解(PCI/PCI-E)5.53PCIE RC侧 地址映射
RK3399平台开发系列讲解(高速设备驱动篇)6.33PCIe 配置空间介绍