PCIe 设备树详细讲解

Posted smartvxworks

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PCIe 设备树详细讲解相关的知识,希望对你有一定的参考价值。

目录

1.基本数据格式

2.基本概念

2.1产品示例

2.2初始结构

2.3处理器

2.4节点名称

2.5设备

2.6了解兼容属性

3.寻址的工作原理

3.1中央处理器寻址

3.2内存映射设备

3.3非内存映射设备

3.4范围(地址转换)

4.中断的工作原理

5.设备特定数据

6.特殊节点

6.1别名节点

6.2选择节点

7.PCIe内容

7.1高级硬件板卡示例

7.2 PCI Host控制器

7.2.1总线编号

7.2.2 地址转换

7.2.3地址转换

7.3高级中断映射


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设备。缺少属性意味着设备不能由其父设备以外的任何设备直接访问。rangesranges

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,0aliases

    aliases 
        ethernet0 = &eth0;
        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.hiphys.midphys.low

phys.hiphys.midphys.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-cellsinterrupt-mapinterrupt-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 配置空间介绍

RK3399平台开发系列讲解(高速设备驱动篇)6.50如何对PCIe设备进行配置操作

RK3399平台开发系列讲解(高速设备驱动篇)6.59PCIe各类接口简介