本文主要介绍如何为新机器编写设备树,它旨在描述设备树的基本概念以及它们如何将其应用于描述机器。有关设备树数据格式的完整技术说明,请参阅 ePAPR v1.1规范。ePAPR规范比本文介绍的有关设备树的描述包含更多详细的信息,请参阅此文以获取本文未涵盖的关于设备树的更高级用法 。
基本数据格式
设备树是节点和属性的简单树型结构。属性是键-值对,节点可以包含属性和子节点。下面的代码是设备树的结构模板,可以先了解一下:
/ DTS-V1 /; / { node1 { a-string-property =“A string”; a-string-list-property =“first string”,“second string”; //十六进制隐含在字节数组中。不需要'0x'前缀. a-byte-data-property = [01 23 34 56]; child-node1 { //boolean,first-child-property定义为true,不定义为false. first-child-boolean-property; second-child-cell-property = <1>; a-string-property =“Hello,world”; }; child-node2 { }; }; node2 { an-empty-property; a-cell-property = <1 2 3 4>; }; };
这棵树显然没用,因为它没有描述任何东西,但它确实显示了节点和属性的结构。既:
- 单个根节点:“ / ”
- 几个子节点:“ node1 ”和“ node2 ”
- node1的几个孩子:“ child-node1 ”和“ child-node2 ”
- 一堆散落在树上的属性。
属性是简单的键值对,其中,值可以为空或包含任意字节流。虽然数据类型未编码到数据结构中,但有一些基本数据表示可以在设备树源文件中表示。
- 文本字符串(以null结尾)用双引号表示:
string-property = "a string";
- 'Cells'是由尖括号分隔的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";
基本概念
要了解设备树的使用方式,最好的方式就是从头构建一棵。下面,我们就从一台简单的机器开始,去逐步构建一棵设备树来描述它。
样机
考虑以下想象的机器(松散地基于ARM Versatile),由“Acme”制造并命名为“Coyote's Revenge”:
- 单核 32位ARM处理器
- 处理器本地总线连接到内存映射串行端口,spi总线控制器,i2c控制器,中断控制器和外部总线桥
- 1个基地址为0的256MB SDRAM
- 2个基于0x101F1000和0x101F2000的串行端口
- 1个基于0x101F3000GPIO控制器
- 1个基于0x10170000的SPI控制器,具有以下器件:
- 带有SS引脚的MMC插槽连接到GPIO#1
- 外部总线桥接器具有以下设备:
- SMC SMC91111以太网设备连接到外部总线,基于0x10100000
- 2c控制器基于0x10160000,具有以下设备: Maxim DS1338实时时钟。响应从站地址1101000(0x58)
- 基于0x30000000的64MB NOR闪存
初始结构
第一步是为机器设置骨架结构。这是有效设备树所需的最小结构。在此阶段,您需属性compatible来唯一标识机器。
/ DTS-V1 /; / { compatible =“acme,coyotes-revenge”; };
compatible 指定系统的名称。它包含一个“,形式的字符串。重要的是指定确切的设备,并包含制造商名称以避免命名空间冲突。由于操作系统将使用该compatible值来决定选择何种架构来启动机器运行,所以将正确的数据放入此属性非常重要。从理论上讲,兼容性是操作系统唯一识别机器所需的所有数据。如果所有机器细节都是硬编码的,那么操作系统可能只需要包含“acme,coyotes-revenge” 的compatible属性。
CPU
下一步是描述每个CPU。为每个CPU添加一个名为“cpus”的容器节点,其中包含一个子节点。下面描述的处理器平台是双核Cortex A9。
/ DTS-V1 /; / { compatible =“acme,coyotes-revenge”; cpus { cpu @ 0 { compatible =“arm,cortex-a9”; }; cpu @ 1 { compatible =“arm,cortex-a9”; }; }; };
每个cpu节点中的兼容属性是一个字符串,它指定表单中的确切cpu模型 , ,就像顶层的兼容属性一样。 稍后将向cpu节点添加更多属性,但我们首先需要讨论更多的基本概
念。
节点名称
值得花点时间谈谈命名约定。每个节点必须在表单中有一个名称 [@ ]。 是一个简单的ascii字符串 最多可以包含31个字符。通常,节点根据它代表的设备类型命名。即,3com以太网适配器的节点将使用名称ethernet ,而不是3com509。
如果节点描述的是具有地址的设备,则需要包括单元地址信息。通常,单元地址是用于访问设备的主要地址,并列在节点的 reg 属性中表示出来。我们将在本文档后面介绍reg属性。
同级节点必须是唯一命名的,这里说的节点名称是name和unit-address,name是可以系统的,但只要地址不同(比如,serial@101f1000和serial@101f2000)就不会有问题。
有关节点命名的完整详细信息,请参阅ePAPR规范的第2.2.1节。
设备
系统中的每个设备都由设备树节点表示。下一步是使用每个设备的节点填充树。现在,新节点将保持为空,直到我们可以讨论如何处理地址范围和irq。
/ 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”; }; }; };
在此树中,已为系统中的每个设备添加了一个节点,层次结构反映了设备连接到系统的方式。即,外部总线上的设备是外部总线节点的子设备,i2c设备是i2c总线控制器节点的子设备。通常,层次结构是从CPU的角度表示的系统视图。此树在此时无效。它缺少有关设备之间连接的信息。该数据将在稍后添加。 在这棵树中要注意的一些事情:
- 每个设备节点都有一个 compatible 属性。
- flash节点在兼容属性中有2个字符串。请继续阅读下一节以了解原因。
- 如前所述,节点名称反映了设备的类型,而不是特定的模型。有关应尽可能使用的已定义通用节点名称列表,请参阅ePAPR规范的第2.2.2节。
了解 compatible Property
树中表示设备的每个节点都需要具有compatible 属性。compatible是操作系统用来决定绑定设备和设备驱动程序的 关键。compatible 是一个字符串列表。列表中的第一个字符串指定节点在表单中表示的确切设备","。剩余的字符串表示设备兼容的其他设备。
例如,飞思卡尔MPC8349片上系统(SoC)具有串行器件,该器件实现了National Semiconductor ns16550寄存器接口。因此, MPC8349串行设备的兼容属性应为: compatible = "fsl,mpc8349-uart", "ns16550" 。在这种情况下,fsl,mpc8349-uart 指定确切的器件,并用ns16550 声明它与National Semiconductor 16550 UART寄存器级兼容。
注意: ns16550 由于历史原因,没有制造商前缀。所有新的兼容值都应使用制造商前缀。这种做法允许将现有设备驱动程序绑定到较新的设备,同时仍然唯一地标识确切的硬件。
警告:不要使用通配符兼容值,例如“fsl,mpc83xx-uart”或类似值。半导体供应商总是会做出改变,在改变它的时候为时已晚,打破了你的通配符假设。相反,选择特定的半导体实现并使所有后续芯片与之兼容。
寻址如何工作
可寻址的设备使用以下属性将地址信息编码到设备树中:
- reg
- #address-cells
- #size-cells
每个可寻址设备获取一个 reg ,其结构为元组列表,形如 reg = 。每个元组表示设备使用的地址范围。每个地址值是一个或多个称为单元的32位整数的列表。 类似地,长度值可以是单元格列表,也可以是空的。
由于地址和长度字段都是可变大小的变量,因此父节点中的 #address-cells 和 #size-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>; }; };
在cpus节点中, #address-cells设置为1,#size-cells设置为0。这意味着子reg值是单个 uint32,并且没有大小字段的地址。在这种情况下,两个cpus被分配地址0和1。#size-cells 对 于cpu节点是0,因为每个cpu仅被分配一个地址。
您还会注意到该 reg 值与节点名称中的值匹配。按照惯例,如果节点具有reg属性,则节点名 称必须包含unit-address,这是reg属性中的第一个地址值。
内存映射设备
与在cpu节点中的单个地址值不同,内存映射设备被分配了一系列地址空间,内存设备的所有操作都被映射到该地址空间中。 #size-cells用于表示每个子reg元组中长度字段的大小。在以下示例中,每个地址值为1个单元(32 位),每个长度值也为1个单元,这在32位系统上是典型的。对于#address-cells和#size-cells, 64位机器可以使用值2来获得设备树中的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设备地址分配了两个地址范围; 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>; }; };
external-bus用了2个cells定义地址值:一个用于芯片的片选,一个用于表示相对于被选芯片的基址偏 移。长度字段保持为单个cell,因为只有地址的偏移部分需要具有范围。因此,在这个例子 中,每个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的地址域,因此不需要任 何显式映射。例如,串行@ 101f0000设备直接分配地址0x101f0000。
不是根的直接子节点的节点不使用CPU的地址域。为了获得内存映射地址,设备树必须指定如何 将地址从一个域转换为另一个域。该ranges property用于此目的。
以下是添加了ranges property的示例设备树。
/ 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”; #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是一个地址翻译列表。范围表中的每个条目都是一个元组,其中包含子地址,父地址和 子地址空间中区域的大小。每个字段的大小取决于子节点的 **#address-cells **值,父节点的#address-cells 值和子节点的 **#size-cells **值。对于我们示例中的外部总线,子地址是2个单 元,父地址是1个单元,大小也是1个单元。ranges范围映射如下:
- 芯片片选为0的偏移为0,其映射到地址范围0x10100000..0x1010ffff
- 芯片片选为1的偏移为0,其映射到地址范围0x10160000..0x1016ffff
- 芯片片选为2的偏移为0,其映射到地址范围0x30000000..0x30ffffff
或者,如果父地址空间和子地址空间相同,则节点可以改为添加空 ranges 属性。空范围属性的 存在意味着子地址空间中的地址以1:1映射到父地址空间。
您可能会问为什么地址转换完全可以用于1:1映射。某些总线(如PCI)具有完全不同的地址空 间,其详细信息需要暴露给操作系统。其具有有DMA引擎的设备,需要知道总线上的真实地址。有时需 要将设备组合在一起,因为它们都共享相同的软件可编程物理地址映射。是否应使用1:1映射在 很大程度上取决于操作系统所需的信息以及硬件设计。
您还应该注意到 ranges i2c @ 1,0节点中没有ranges property。这样做的原因是,与外部总线不同,i2c总线 上的设备不在CPU的地址域中进行内存映射。相反,CPU通过i2c @ 1,0设备间接访问rtc @ 58设 备。缺少 ranges 属性意味着除了父级之外的任何设备都不能直接访问设备。
中断如何工作
与遵循树的自然结构的地址范围转换不同,中断信号可以源自并终止于机器中的任何设备。与在 设备树中自然表达的设备寻址不同,中断信号表示为独立于树的节点之间的链路。四个属性用于 描述中断连接:
- interrupt-controller - 一个空属性,将节点声明为接收中断信号的设备,即中断控制器。
- #interrupt-cells 这是中断控制器节点的属性。它说明了该中断控制器的中断说明符中有多少个单元(类似于 #address-cells 和 #size-cells )。
- interrupt-parent 设备节点的属性,包含与其连接的中断控制器的phandle(pointer handle)。没有interrupt-parent属性的节点也可以从其父节点继承该属性。
- interrupts 设备节点的属性,包含一个interrupt specifiers列表,每一个用于表示设备上的每个中断输出信号。
- interrupt-names对于设备节点的"interrupts"进行命名,驱动程序可以依据该命令查询对应的中断号。
一个interrupt specifier是数据的一个或多个cells(如通过#interrupt-cells定义),指定哪个中断输入被设备关联 到。大多数器件只有一个中断输出,如下例所示,但可以在器件上有多个中断输出。interrupt specifier的含义完全取决于其所绑定的中断控制器设备。每个中断控制器可以决定唯一定义中断输入所需的单元数(cell数量)。
以下代码为我们的Coyote Revenge示例机器添加了中断连接:
/ DTS-V1 /; / { compatible =“acme,coyotes-revenge”; #address-cells = <1>; #size-cells = <1>; interrupt-parent = <&intc>; //phandle = <&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>; }; gpio0 @ 101f3000 { compatible =“arm,pl061”; reg = <0x101f3000 0x1000 0x101f4000 0x0010>; interrupts = <3 0>; gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; }; gpio1 @ 101f5000 { compatible =“arm,pl061”; reg = <0x101f3000 0x1000 0x101f4000 0x0010>; interrupts = <4 0>; gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; }; 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>; }; }; };
有些事情需要注意:
- 机器有只有一个主中断控制器,中断控制器@10140000, 可以存在多个从中断控制器,例如,gpio0 @ 101f3000、gpio1 @ 101f5000等,其与主中断控制形成级联关系。
- 标签'intc:'已添加到中断控制器节点,标签用于将phandle分配给根节点中的interrupt-parent属性。此interrupt-parent为系统的默认值,因为除非显式覆盖它,否则所有子节点都会继承它。
- 每个器件都使用interrupt属性来指定不同的中断输入线(interrupt input line)。
- #interrupt-cells为2,因此每个中断说明符都有2个cells。此示例使用第一个cell编码设备所使用的中断号,第二个cell用于编码中断属性标志,例如高电平触发还是低电平触发,或边沿触发亦或是电平敏感触发。对于任何给定的中断控制器,请参阅控制器的绑定文档以了解如何编码interrupt specifier。
设备特定数据
除了公共属性之外,还可以向节点添加任意属性和子节点。只要遵循一些规则,就可以添加操作系统所需的任何数据。
- 首先,新的特定于设备的属性名称应使用制造商前缀,以便它们不会与现有的标准属性名称冲突。
- 其次,必须在绑定中记录属性和子节点的含义,以便设备驱动程序作者知道如何解释数据。绑定文档描述特定兼容值的含义,其描述:它应具有的属性,可能具有的子节点以及它所代表的设备。每个唯一 compatible 值应具有自己的绑定(或声明与另一个兼容值的兼容性)。此Wiki中记录了新设备的绑定。有关文档格式和审阅过程的说明,请参见主页
- 第三,在devicetree-discuss@lists.ozlabs.org邮件列表上发布新的绑定以供审核。检查新绑定会捕获许多常见错误,这些错误将在未来引发问题。
特殊节点
aliases 节点
特定节点通常由完整路径引用 /external-bus/ethernet@0,0 ,但是当用户真正想知道的是“哪个设备是eth0?”时,这会很麻烦。aliases 节点可用于为完整设备路径分配短别名。例如:
aliases{ ethernet0 = ð0; serial0 = &serial0; };
操作系统鼓励在为设备分配标识符时使用别名。
你会注意到这里使用的新语法。 property = &label; 该语法指定由标签作为字符串属性引用的完整节点路径。这与之 前使用的将phandle值插入cell的形式不同,既phandle = < &label >;
chosen 节点
chosen 节点不代表真实设备,而是用于指定在bootloader和操作系统之间传递数据的地方,如引导参 数。chosen节点中的数据不代表硬件。通常,chosen节点在.dts源文件中保留为空,并在引导时填充。 在我们的示例系统中,固件可能会将以下内容添加到所选节点:
chosen{ bootargs =“root = / dev / nfs rw nfsroot = 192.168.1.1 console = ttyS0,115200”; };
高级主题
高级样品机
现在我们已经定义了基础知识,让我们在样本机器上添加一些硬件来讨论一些更复杂的用例。
高级示例计算机添加了一个PCI主桥,其控制寄存器映射到0x10180000,而BARs则被编程为在地址0x80000000之上启动。
鉴于我们已经了解的设备树,我们可以从添加以下节点开始描述PCI主桥。
pci @ 10180000 { compatible =“arm,versatile-pci-hostbridge”,“pci”; reg = <0x10180000 0x1000>; interrupts = <8 0>; };
PCI主桥
本节介绍Host/PCI bridge节点。
注意,本节假设了一些PCI的基本知识。这不是关于PCI的教程,如果您需要更深入的信息,请阅读 。您还可以参考 ePAPR v1.1 或PCI Bus Binding to Open Firmware。可在此处找到飞思卡尔MPC5200的完整工作示例。
PCI总线编号
每个PCI总线段都是唯一编号的,并且使用 bus-ranges 在pci节点中公开总线编号,bus-ranges包含两个cells单元。第一个cell给出分配给该节点的总线编号,第二个cell给出任何从属PCI总线的最大总线编号。
样机具有单个pci总线,因此两个单元都为0。
pci @ 0x10180000 { compatible = “arm,versatile-pci-hostbridge”,“pci”; reg = <0x10180000 0x1000>; interrupts = <8 0>; bus-ranges = <0 0>; };
PCI地址转换
与前面描述的本地总线类似,PCI地址空间与CPU地址空间完全分离,因此需要进行地址转换才能从PCI地址转换到CPU地址。一如往常,这是使用完成range, #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个cells,PCI范围编码为2个cells。第一个问题可能是,为什么我们需要三个32位cells来指定PCI地址。这三个cells分别表示为: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和phys.low。然而,真正有趣的事情是在phys.high,这是一 个有位域:
- n :可重定位区域标志(此处不起作用)
- p :prefetchable(可缓存)区域标志
- t :别名地址标志(此处不起作用)
- ss :空间代码
- 00:配置空间
- 01:I / O空间
- 10:32位存储空间
- 11:64位存储空间
- bbbbbbbb :PCI总线编号。PCI可以分层结构化。所以我们可能有PCI/PCI桥接器,它将定义子总线。
- ddddd :设备编号,通常与IDSEL信号连接相关联。
- fff :功能号码。用于多功能PCI设备。
- rrrrrrrr : 注册号; 用于配置周期。
出于PCI地址转换的目的,重要的字段是 p 和 ss 。phys.hi中p和ss的值决定了访问哪个PCI地址空间。因此,查看我们的范围属性,我们有三个区域:
- 一个32位可预取的存储区,从512 MB大小的PCI地址0x80000000开始,将映射到主机CPU上的地址0x80000000。
- 一个32位的非预取内存区域,从256 MB大小的PCI地址0xa0000000开始,将映射到主机CPU上的地址0xa0000000。
- 一个I/O区域,从16 MB大小的PCI地址0x00000000开始,将映射到主机CPU上的地址0xb0000000。
为了使wrench投入工作,phys.hi位域的存在意味着操作系统需要知道该节点代表PCI桥,以便它可以忽略不相关的字段以进行翻译。操作系统将在PCI总线节点中查找字符串“pci”,以确定是否需要屏蔽额外字段。
PCI DMA地址转换
上述范围定义了CPU如何查看PCI内存,并帮助CPU设置正确的内存窗口并将正确的参数写入各种PCI设备寄存器。这有时称为outbound memory。
地址转换的一个特例涉及PCI主机硬件如何看待系统的核心内存。当PCI主控制器充当主控制器,并独立访问系统的核心存储器时,会发生这种情况。由于这通常与CPU的视图不同(由于存储器线的连接方式),因此可能需要在初始化时将其编程到PCI主控制器中。这被视为一种DMA,因为PCI总线独立地执行直接存储器访问,因此映射被命名为dma-ranges。这种类型的内存映射有 时称为inbound memory,不是PCI设备树规范的一部分。
在某些情况下,ROM(BIOS)或类似设备将在启动时设置这些寄存器,但在其他情况下,PCI控制器完全未初始化,并且需要从设备树设置这些转换。然后,PCI主机驱动程序通常会解析dma-ranges属性并相应地在主机控制器中设置一些寄存器。
扩展上面的示例:
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 dma-ranges = <0x02000000 0 0x00000000 0x80000000 0 0x20000000 >; };
此dma-ranges条目表示从PCI主机控制器的角度来看,PCI地址0x00000000处的512MB将出现在主核心存储器中的地址0x80000000处。如您所见,我们只是将ss地址类型设置为0x02,表明这是一些32位内存。
高级中断映射
现在我们来到最有趣的部分,PCI中断映射。PCI设备可以使用#INTA,#INTB,#INTC和#INTD触发中断。单功能设备有义务使用#INTA进行中断。如果多功能设备使用单个中断引脚,则必须使用#INTA,如果使用两个中断引脚,则必须使用#INB等等。由于这些规则,#INTA通常比#INTB,#INTC,#INTD具有更多的功能。为了分配中断#INTD到中断#INTA四个中断到四条IRQ中断线路上,每个PCI插槽或设备通常以旋转方式连接到中断控制器上的不同输入,以避免所有#INTA客户端连接到同一个输入中断线。此过程被称为混写(swizzling)中断。因此,设备树需要一种将每个PCI中断信号映射到中断控制器输入的方法。 #interrupt-cells , interrupt-map 和** interrupt-mapmask** 属性用于描述该中断的映射。
实际上,这里描述的中断映射不仅限于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,这与使用2个cells的系统中断控制器不同; 一个用于 irq中断号,一个用于中断属性标志。PCI仅需要一个cell用于中断,因为PCI中断被指定为始终是低电平敏感的。
在我们的示例板中,我们有2个PCI插槽,分别有4个中断线,因此我们必须将8个中断线映射到中断控制器。这是使用interrupt-map属性完成的。中断映射的确切过程在描述。
由于中断号(#INTA等)不足以区分单个PCI总线上的多个PCI设备,因此我们还必须表示哪个PCI设备触发了中断线。幸运的是,每个PCI设备都有一个我们可以使用的唯一设备号。为了区分几个PCI设备的中断,我们需要一个由PCI设备号和PCI中断号组成的元组。更一般地说,我们构造一个单元中断说明符,它有四个单元格:
- 三个#address-cells由phys.hi,phys.mid,phys.low和
- 一个#interrupt-cell(#INTA,#INTB,#INTC,#INTD)。
因为我们只需要PCI地址的设备号部分,所以interrupt-map-mask属性起作用。
interrupt-mapmask也是一个像元素中断说明符一样的4元组。掩码中的1表示应考虑单元中断说明符的哪一部分。在我们的例子中,我们可以看到只需要phys.hi的设备号部分,我们需要3位来区分四条中断线(计数PCI中断线从1开始,而不是0!)。
现在我们可以构造interrupt-map属性。此属性是一个表,该表中的每个条目都包含一个子(PCI总线)单元中断说明符,一个父句柄(负责提供中断的中断控制器)和一个父单元中断说明符。因此,在第一行中,我们可以读到PCI中断#INTA映射到IRQ9,我们的中断控制器是低电平的。
目前唯一缺少的部分是PCI总线单元中断说明符中的奇怪数字。单元中断说明符的重要部分是来自phys.hi位字段的设备编号。设备编号是特定于板的,它取决于每个PCI主控制器如何激活每个设备上的IDSEL引脚。在该示例中,PCI插槽1被分配设备ID 24(0x18),并且PCI插槽2被分配设备ID25(0x19)。每个时隙的phys.hi值是通过将器件编号向上移位11位到位域的ddddd部分来确定的,如下所示:
- 插槽1的phys.hi是0xC000,并且
- 插槽2的phys.hi是0xC800。
将所有内容放在一起显示中断映射属性:
- 插槽1的#INTA是IRQ9,在主中断控制器上是低电平敏感的
- 插槽1的#INTB是IRQ10,在主中断控制器上是低电平敏感的
- 插槽1的#INTC是IRQ11,在主中断控制器上是低电平敏感的
- 插槽1的#INTD是IRQ12,在主中断控制器上是低电平敏感的
和
- 插槽2的#INTA是IRQ10,在主中断控制器上是低电平敏感的
- 插槽2的#INTB是IRQ11,在主中断控制器上是低电平敏感的
- 插槽2的#INTC是IRQ12,在主中断控制器上是低电平敏感的
- 插槽2的#INTD是IRQ9,在主中断控制器上是低电平敏感的
该** interrupts = <8 0>**; 属性描述了HOST/PCI桥控制器本身可能触发的中断。不要将这些中断与PCI设备可能触发的中断混淆(使用INTA,INTB,...)。
最后要注意的一件事。与中断父属性一样,节点上存在中断映射属性将更改所有子节点和孙节点的默认中断控制器。在此PCI示例中,这意味着PCI主桥成为默认的中断控制器。如果通过PCI总线连接的设备与另一个中断控制器直接连接,则还需要指定自己的中断父属性。