Device Tree Usage(三)

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 = &eth0;
        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.midphys.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.midphys.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地址转换的目的,重要的字段是pss。在phys.hi中的pss值决定了那个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-cellsinterrupt-mapinterrupt-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 specifierparent 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

  1.  Tom Shanley / Don Anderson: PCI System Architecture. Mindshare Inc.
  2. PCI Bus Bindings to Open Firmware.
  3. Open Firmware Recommended Practice: Interrupt Mapping
  4. PCI interrupts are always level low sensitive.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值