How Interrupts Work
与遵循树的自然结构的地址范围转换不同,中断信号可以在机器上的任何设备产生和终止。与在设备树中自然表达的设备寻址不同,中断信号被表示为独立于树的节点之间的链接。有四个属性用于描述中断连接:
- interrupt-controller - 一个空属性,声明一个节点作为接收中断信号的设备
- #interrupt-cells - 这是一个中断控制器节点属性。它表明这个中断控制器的 interrupt specifier 中有多少cells(类似于 #address-cells 和 #size-cells)。
- interrupt-parent - 一个设备节点的属性,包含一个phandle到它所连接的中断控制器。没有interrupt-parent属性的节点也可以从其父节点继承该属性。
- interrupts - 一个设备节点的属性,包含interrupt specifiers列表,一个用于设备上的每个中断输出信号。
interrupt specifier 是一个或多个数据cells(由#interrupt-cells指定),它指定了设备附加到那个中断输入。大多数设备仅有一个中断输出,如下面的例子所示,但是在设备上有多个中断输出是可能的。interrupt specifier 的含义完全依赖于中断控制器设备的绑定。每个中断控制器可以决定需要多少cells来唯一定义中断输入。
下面的代码为我们的Coyote's 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:'已经被添加进中断控制器节点,并且该标签被用来在根节点中分配一个phandle到interrupt-parent属性。这个interrupt-parent值成为系统默认值,因为所有子节点都会继承它,除非它被显式的覆盖。
- 每个设备用一个interrupt属性来指定一个不同的中断输入行。
- #interrupt-cells 值是2,因此每一个interrupt specifier 有 2 cells。这个例子使用常见模式,用第一个cell 编码中断行号,第二个cell 来编码诸如高有效、低有效或者边缘触发,水平敏感的标志。对于任何给定的中断控制器,请参考控制器的绑定文档来了解如何编码specifier。
Device Specific Data
除了公共属性之外,可以将任意属性和子节点添加到节点中。只要遵循一些规则,就可以添加操作系统所需的任何数据。
首先,新的device-specific 属性名称应该使用一个制造前缀,这样它们就不会与现有的标准属性名称相冲突。
其次,属性和子节点的含义必须在binding中记录,以便设备驱动程序的作者知道如何解释数据。一个binding 记载着一个特定的compatible值意味着什么,它应该拥有什么属性,它可能拥有什么子节点,以及它代表什么设备。每个惟一的compatible值都应该有自己的binding(或声称与另一个compatible值的兼容性)。在这个wiki中记录了新设备的bindings。有关文档格式和评审过程的描述,请参阅Main Page。
第三,发布新的bindings,以便在devicetree-discuss@lists.ozlabs.org邮件列表上进行审查。检查新的bindings会捕获许多常见的错误,这些错误将在未来造成问题。
Special Nodes
aliases Node
一个特定的节点通常通过完整的路径引用,如 /external-bus/ethernet@0,0 ,然而当用户真正想知道"which device is eth0?"时,那就变得麻烦了。aliases 节点可以用来为完整的设备路径分配一个简短的别名。例如:
aliases {
ethernet0 = ð0;
serial0 = &serial0;
};
在为设备分配标识符时,操作系统欢迎使用别名。
您会注意到这使用了一种新的语法。property = &label; 语法将标签引用的完整节点路径指定为字符串属性。这与phandle=<&label>;不同,先前使用的形式是将phandle值插入到cell中。
chosen Node
chosen节点不代表真实的设备,而是充当固件和操作系统之间传递数据的地方,如启动参数。在chosen 节点中的数据不代表硬件。通常chosen节点在.dts源文件中被清空,并在引导时填充。在我们的示例系统中,固件可能添加以下内容到chosen节点:
chosen {
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};
Advanced Topics
Advanced Sample Machine
现在我们已经了解了一些基本定义,让我们加入一些硬件到样例机器中来讨论一些更复杂的使用情形。
高级样例机器添加一个PCI桥,其控制寄存器内存映射到0x10180000,并且BARs程序在地址0x80000000上启动。
根据我们已经知道的关于设备树的知识,我们可以从添加以下节点开始,来描述PCI主机桥。
pci@10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
};
PCI Host Bridge
这节描述Host/PCI 桥节点。
注意,在本节中假设已经有一些PCI的基本知识。这不是关于PCI的教程,假如您需要一些更深层的信息,请阅读[1]。您可以参考 或者 。
PCI Bus numbering
每个PCI总线段是唯一编号的,总线编号通过使用bus-ranges属性在PCI节点中公开,该属性包含2个cells。第一个cell给出分配给这个节点的总线号,第二个cell给出任何一个从属PCI总线的最大总线数。
样例机器有一个单独的pci总线,所以两个cells 都是0。
pci@0x10180000 {
compatible = "arm,versatile-pci-hostbridge", "pci";
reg = <0x10180000 0x1000>;
interrupts = <8 0>;
bus-ranges = <0 0>;
};
PCI Address Translation
与先前描述的本地总线相似,PCI地址空间与CPU地址空间是完全分离的,因此从PCI地址到CPU地址的地址转换时需要的。一如既往,这个使用ranges, #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。第一个问题可能是,为什么我们需要3个32位cells 来指定一个PCI地址。这三个cells被标记为 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位宽,被编码进phys.mid和phys.low。然而,真正有意思的事情在phys.high,一个位字段:
- n: 可重定位区域标志(这里不扮演角色)
- p: 可预取(可缓存)区域标志
- t: 别名地址标志(这里不扮演角色)
- ss: 空间代码
- 00: 配置空间
- 01: I/O空间
- 10: 32位地址空间
- 11: 64位地址空间
- bbbbbbbb: PCI总线号。PCI可以是分层结构的。所以我们可能有pci/pci桥,它将定义子总线。
- ddddd: 设备号,通常与IDSEL信号连接有关。
fff
: 功能号。用于多功能PCI设备。rrrrrrrr
: Register Number(注册号?寄存器号?),用于配置周期。
对于PCI地址转换的目的,重要的字段是p 和 ss。在phys.hi中的p 和 ss值决定了那个PCI地址正在被访问。因此,看我们的ranges 属性,我们有三个区域:
- 一个32位的可预取内存区域,从PCI地址0x80000000开始,512MB大小,映射到主机CPU的地址0x80000000。
- 一个32位的不可预取内存区域,从PCI地址0xa0000000开始,256MB大小,映射到主机CPU的地址0xa0000000。
- 一个I/O区域,从PCI地址0x00000000开始,16MB大小,映射到主机CPU的地址0xb0000000。
To throw a wrench into the works(妨碍工作?),phys.hi bitfield存在意味着操作系统需要知道节点代表着一个PCI桥,这样它就可以忽略无关字段,达到转换的目的。操作系统将在PCI总线节点中搜索字符串"pci",以确定它是否需要屏蔽额外的字段。
PCI DMA Address Translation
以上范围定义了CPU如何看待PCI内存,帮助CPU设置正确的内存窗口和写入正确的参数到各种PCI的设备寄存器。这个有时被称作outbound memory。
一个特殊的地址转换情形关注PCI主机硬件如何看待系统的核心内存。当PCI主机控制器充当主服务器并独立访问系统的核心内存时,就会发生这种情况。由于这通常与CPU的视图不同(由于内存线是如何连接的),这可能需要在初始化过程中被编程到PCI主机控制器中。这被看作是一种DMA,因为PCI总线独立地执行直接的内存访问,因此,映射被命名为dma-range。这种类型的内存映射有时被称为inbound memory,而不是PCI设备树规范的一部分。
在某些情况下,ROM(BIOS)或类似的东西会在引导时设置这些寄存器,但是在其他情况下,PCI控制器是完全未初始化的,这些转换需要从设备树中进行设置。然后,PCI主机驱动程序通常会解析dma-射程属性,并相应地在主机控制器中设置一些寄存器。
在上面的例子中扩展:
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的512 MB将出现在地址0x80000000的主核心存储器中。正如您所看到的,我们只是将ss地址类型设置为0x02,表示这是32位内存。
Advanced Interrupt Mapping
现在我们来看下最有趣的部分,PCI中断映射。一个PCI设备可以使用#INTA, #INTB, #INTC 和 #INTD线触发中断。一个单功能设备责无旁贷的使用#INTA来进行中断。多功能设备假如它使用一个单独中断引脚必须使用#INTA,如果它使用两个中断引脚必须是#INTA和#INTB等等。由于这些规则,#INTA通常比#INTB,#INTC和#INTD被更多功能使用。为了将负载分布到#INTA到#INTD的四个IRQ行,每个PCI插槽或设备通常以旋转的方式连接到中断控制器上的不同输入,以避免所有#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-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中断被指定为总是低级别的敏感度。
在我们的示例板中,我们有两个PCI槽,分别有4根中断线,因此我们必须将8根中断线映射到中断控制器。这个使用interrupt-map属性完成。中断映射精确过程描述在[3]中。
由于中断数量(#INTA 等等)不足以区分单个PCI总线上的几个PCI设备,我们还必须表示哪个PCI设备触发了中断线。幸运的是,每个PCI设备都有一个我们可以使用的唯一的设备号。为了区分几个PCI设备的中断,我们需要一个由PCI设备号和PCI中断编号组成的元组。更一般地说,我们构建一个unit interrupt specifier,它有四个cells:
- 三个由phys.hi,phys.mid,phys.low组成的#address-cells
- 一个 #interrupt-cell (#INTA, #INTB, #INTC, #INTD)。
因为我们只需要PCI地址的设备号部分,所以interrupt-map-mask属性就发挥作用了。interrupt-map-mask也是一个4元组,就像unit interrupt specifier一样。在掩码中,1表示unit interrupt specifier的哪一部分应该被考虑进去。在我们的例子中,我们可以看到只有phys.hi的设备编号部分是必需的,我们需要3位来区分这四个中断行(计算PCI中断行从1开始,而不是在0!)。
现在我们可以构造interrupt-map属性。这个属性是一张表,这个表中的每个条目都由一个child(PCI总线)unit interrupt specifier、parent handle(负责服务中断的中断控制器)和parent unit interrupt specifier组成。所以在第一行中,我们可以看到PCI中断#INTA 被映射到IRQ 9,我们的中断控制器的级别低敏感[4]。
目前唯一缺少的部分是PCI总线unit interrupt specifier的奇怪数字。unit interrupt specifier的重要部分是来自phys.hi位域的设备编号。设备号是特定于板的,这取决于每个PCI主机控制器如何激活每个设备上的IDSEL pin。在这个例子中,PCI插槽1被分配到设备id 24(0x18),而PCI插槽2被分配设备id 25(0x19)。每个槽的phys.hi的值都是通过将设备编号由11位转移到位字段的ddddd部分来确定的,如下:
- phys.hi for slot 1 is 0xC000, and
- phys.hi for slot 2 is 0xC800.
Putting it all together the interrupt-map property show:
- #INTA of slot 1 is IRQ9, level low sensitive on the primary interrupt controller
- #INTB of slot 1 is IRQ10, level low sensitive on the primary interrupt controller
- #INTC of slot 1 is IRQ11, level low sensitive on the primary interrupt controller
- #INTD of slot 1 is IRQ12, level low sensitive on the primary interrupt controller
and
- #INTA of slot 2 is IRQ10, level low sensitive on the primary interrupt controller
- #INTB of slot 2 is IRQ11, level low sensitive on the primary interrupt controller
- #INTC of slot 2 is IRQ12, level low sensitive on the primary interrupt controller
- #INTD of slot 2 is IRQ9, level low sensitive on the primary interrupt controller
interrupts = <8 0>;属性描述host/PCI-bridge控制器本身可能触发的中断。不要将这些中断和PCI设备可能触发(使用INTA,INTB...)的中断相混淆。
最后需要注意的一点。就像interrupt-parent属性一样,节点上interrupt-map属性的存在将改变所有子节点和孙子节点的默认中断控制器。在这个PCI示例中,那意味着PCI主机桥变成了默认中断控制器。如果通过PCI总线连接的设备与另一个中断控制器有直接连接,那么它还需要指定自己的interrupt-parent属性。
Notes
- Tom Shanley / Don Anderson: PCI System Architecture. Mindshare Inc.
- PCI Bus Bindings to Open Firmware.
- Open Firmware Recommended Practice: Interrupt Mapping
- PCI interrupts are always level low sensitive.