设备树用法(Device Tree Usage)中文

  

设备树用法

 

   (本篇文章译自 Device_Tree_Usage)
 

  本页面介绍了如何为新机器编写设备树。 它旨在提供设备树概念的概述以及它们如何用于描述机器。

  有关设备树数据格式的完整技术说明,请参阅ePAPR v1.1规范。 ePAPR规范比本页面介绍的基本主题包含更多详细信息,请参阅此页面以获取本页未涵盖的更高级用法。 ePAPR目前正在使用Devicetree规范文档的新名称进行更新。

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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" 和" 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";

2 基本概念

  要了解设备树的使用方式,我们将从一台样机开始,并构建一个设备树来逐步描述它。

2.1 样机

  假设有如下一台由“Acme”公司制造的、“Coyote's Revenge”为名字的样机(基于ARM Versatile架构):

  • One 32bit ARM CPU
  • processor local bus attached to memory mapped serial port, spi bus controller, i2c controller, interrupt controller, and external bus bridge
  • 256MB of SDRAM based at 0
  • 2 Serial ports based at 0x101F1000 and 0x101F2000
  • GPIO controller based at 0x101F3000
  • SPI controller based at 0x10170000 with following devices
    • MMC slot with SS pin attached to GPIO #1
  • External bus bridge with following devices
    • SMC SMC91111 Ethernet device attached to external bus based at 0x10100000
    • i2c controller based at 0x10160000 with following devices
      • Maxim DS1338 real time clock. Responds to slave address 1101000 (0x58)
    • 64MB of NOR flash based at 0x30000000

2.2 初始结构

  第一步是为设备编写主结构。 这是有效设备树所需的最小结构。 在此阶段,您需要唯一标识机器。

/dts-v1/;

/ {
    compatible = "acme,coyotes-revenge";
};

  compatible指定系统的名称。 它包含一个“<manufacturer>,<model>形式的字符串。重要的是指定确切的设备,并包含制造商名称以避免命名空间冲突。因为操作系统将使用兼容的值来决定如何 要在计算机上运行,将正确的数据放入此属性非常重要。从理论上讲,兼容性是操作系统唯一识别机器所需的所有数据。 如果所有机器细节都是硬编码的,那么操作系统可能会在最顶层compatible属性中专门寻找"acme,coyotes-revenge"。

2.3 CPUs

  下一步是描述每个CPU。 添加一个名为“cpus”的子节点,其包含两个CPU子节点。 在这里,该系统采用的是ARM的双核Cortex A9系统。

/dts-v1/;

/ {
    compatible = "acme,coyotes-revenge";

    cpus {
        cpu@0 {
            compatible = "arm,cortex-a9";
        };
        cpu@1 {
            compatible = "arm,cortex-a9";
        };
    };
};

  每个cpu节点中的 compatible属性是一个字符串,它以<manufacturer>,<model>的形式指定确切的cpu模型,就像顶层的compatible属性一样。稍后将向cpu节点添加更多属性,但我们首先需要讨论更多基本概念。

2.4 节点名称

  花点时间谈谈命名规则是值得的。 每个节点都必须具有<name> [@ <unit-address>]格式的名称。

  <name>是一个简单的ASCII字符串,最长可达31个字符。 通常,节点根据它代表的设备类型命名。例如, 3com以太网适配器的节点以 ethernet命名,而不是以3com509。

  如果节点描述具有地址的设备,则包括单元地址。 通常,单元地址是用于访问设备的主要地址,并列在节点的reg属性中。 我们将在本文档后面介绍reg属性。

  同级节点必须是唯一命名的,但只要地址不同(即serial @ 101f1000和serial @ 101f2000),多个节点使用相同的通用名称是正常的。有关节点命名的完整详细信息,请参阅ePAPR规范的第2.2.1节。

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";
        };
    };
};

  在这个DTS文件中,已为系统中的每个设备添加了一个节点,层次结构反映了设备连接到系统的方式。例如, 外部总线上的设备是外部总线节点的子设备,i2c设备是i2c总线控制器节点的子设备。 通常,层次结构从CPU的角度表示系统的视图。

  此DTS文件在此时无效。 它缺少有关设备之间连接的信息。 该数据将在稍后添加。

 在这个DTS文件中要注意的一些事情::

  • 每个设备节点都具有 compatible 属性。.
  • flash节点在compatible属性中有2个字符串。 请继续阅读下一节以了解原因。
  • 如前所述,节点名称反映了设备的类型,而不是特定的模型。 有关应尽可能使用的已定义通用节点名称列表,请参阅ePAPR规范的第2.2.2节。

2.6 认识 compatible 属性

  树中表示设备的每个节点都需要具有compatible属性。compatible是操作系统用来适配设备的设备驱动程序的关键。

  compatible是一个字符串列表。列表中的第一个字符串指定节点以“<manufacturer>,<model>”形式表示的确切设备。后面的字符串表示设备兼容的其他设备。

  例如,飞思卡尔MPC8349片上系统(SoC)具有串行外设,该外设实现了National Semiconductor的ns16550寄存器接口。因此,MPC8349串行设备的compatible属性应为:

compatible =“fsl,mpc8349-uart”,“ns16550”。

  在这种情况下,fsl,mpc8349-uart指定确切的器件,ns16550声明它与National Semiconductor 16550 UART寄存器级兼容。

   注意:ns16550纯粹由于历史原因没有制造商前缀。 所有新的兼容值都应使用制造商前缀。

这种做法允许将现有设备驱动程序适配到较新的设备,同时仍然唯一地标识确切的硬件。

  警告:不要使用通配符兼容值,例如“fsl,mpc83xx-uart”或类似值。 芯片供应商总是会做出改变,打破了你的通配符设定,再想改变时已晚。 相反,选择特定的芯片类型可使所有后续芯片与之兼容。

3 如何寻址

  可寻址的设备使用以下属性将地址信息编码到设备树中::

  • reg
  • #address-cells
  • #size-cells

  每个可寻址设备获得一个reg,它是reg = <address1 length1 [address2 length2] [address3 length3] ...>形式的数组列表。 每个数组表示设备使用的地址范围。 每个地址值( cells)是一个或多个32位的整数值。 与此类似,地址长度值可以是 cells列表,或者为空。

  由于地址和长度字段都是可变的变量,因此父节点中的#address-cells和#size-cells属性用于表示每个字段中有多少个单元。 或者换句话说,正确解释reg属性需要父节点的#address-cells和#size-cells值。 要了解这一切是如何工作的,我们可以从CPU开始,将寻址属性添加到示例设备树中。

3.1 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数值,size字段为空。 在这种情况下,两个cpus被分配地址0和1。#size-cells对于cpu节点是0,因为每个cpu仅被分配一个地址。

您还会注意到reg值与节点名称中的值匹配。 按照惯例,如果节点具有reg属性,则节点名称必须包含单位地址,这是reg属性中的第一个地址值。

3.2  直接寻址设备

  与在cpu节点中找到的单个地址值不同,内存映射设备被分配一系列将响应的地址。 #size-cells用于表示每个子regs数组中每个字段的长度大小。

在以下示例中,这在32位系统上是常见的,每个地址值为1个单元(32位),每个长度值也为1个单元。 在64位机器的设备树中 ,#address-cells和#size-cells可以使用值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个单元作为地址值; 一个用于芯片选择号,一个用于从芯片选择的基地址偏移值。 长度字段依然为1个单元,因为部分地址偏移值需要具有范围。 因此,在此示例中,每个reg条目包含3个单元; 片选值,偏移量和长度。

由于地址域包含在节点及其子节点中,因此父节点可以自由定义对总线有意义的任何寻址方案。 直接父节点和子节点之外的节点通常不必关心本地寻址域,并且必须映射地址以从一个域到另一个域。

3.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>;
            };
        };

3.4 Ranges属性 (地址转换)

  我们已经讨论了如何为设备分配地址,但此时这些地址只是设备节点的本地地址。 它尚未描述如何从这些地址映射到CPU可以使用的地址。

  根节点始终描述CPU的地址空间视图。 根节点的子节点已经在使用CPU的地址域,因此不需要任何显式映射。 例如,串行@ 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是地址转换的列表。 ranges表中的每个条目都是一个元组,其中包含子地址,父地址和子地址空间中区域的大小。 每个字段的大小取决于子节点的#address-cells值,父节点的#address-cells值和子节点的#size-cells值。 对于我们示例中的外部总线,子地址是2个单元,父地址是1个单元,大小也是1个单元。 三个 ranges刚好可以转换

  • Offset 0 from chip select 0 is mapped to address range 0x10100000..0x1010ffff
  • Offset 0 from chip select 1 is mapped to address range 0x10160000..0x1016ffff
  • Offset 0 from chip select 2 is mapped to address range 0x30000000..0x30ffffff

  另外,如果父地址空间和子地址空间相同,则节点可以改为添加空  ranges属性。空  ranges属性的存在意味着子地址空间中的地址以1:1映射到父地址空间。

  您可能会问为什么地址转换完全可以用于1:1映射。某些总线(如PCI)具有完全不同的地址空间,其详细信息需要传递给操作系统。其他人有DMA引擎,需要知道总线上的真实地址。有时需要将设备组合在一起,因为它们都共享相同的软件可编程物理地址映射。是否应使用1:1映射在很大程度上取决于操作系统所需的信息以及硬件设计。

  您还应该注意到i2c @ 1,0节点中没有  ranges属性。这样做的原因是,与外部总线不同,i2c总线上的设备不在CPU的地址域中进行内存映射。相反,CPU通过i2c @ 1,0设备间接访问rtc @ 58设备。缺少  ranges属性意味着除了父设备之外的任何设备都不能直接访问设备。

4 How Interrupts Work

  与遵循设备树的自然结构的地址范围转换不同,中断信号可以源自并终止于机器中的任何设备。 与在设备树中自然表达的设备寻址不同,中断信号表示为独立于树的节点之间的链路。 四个属性用于描述中断连接:

  • interrupt-controller  - 一个空属性,将节点声明为接收中断信号的设备
  • #interrupt-cells      - 这是中断控制器节点的属性。 它说明了该中断控制器的中断说明符中有多少个单元(类似于#address-cells和#size-cells)。
  • interrupt-parent          - 设备节点的属性,包含与其连接的中断控制器的phandle。 没有interrupt-parent属性的节点也可以从其父节点继承该属性。
  • interrupts            - 设备节点的属性,包含一个中断说明符列表,一个用于设备上的每个中断输出信号。

  中断说明符是一个或多个数据单元(由#interrupt-cells指定),用于指定设备所连接的中断输入。 大多数器件只有一个中断输出,如下例所示,但可以在器件上有多个中断输出。 中断说明符的含义完全取决于中断控制器设备的绑定。 每个中断控制器可以决定唯一定义中断输入所需的单元数。

  以下代码为我们Coyote的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>;
        };
    };
};

需要注意的事项:

  • 机器有一个中断控制器,中断控制器@ 10140000。
  • 标签'intc:'已添加到中断控制器节点,标签用于将phandle分配给根节点中的interrupt-parent属性。 此中断父值成为系统的默认值,因为除非显式覆盖它,否则所有子节点都会继承它。
  • 每个器件都使用中断属性来指定不同的中断输入线。
  • #interrupt-cells为2,因此每个中断说明符都有2个单元。 此示例使用使用第一个单元格编码中断行号的公共模式,以及编码标志的第二个单元格,例如活动高电平与低电平有效,或边沿与电平敏感。 对于任何给定的中断控制器,请参阅控制器的绑定文档以了解如何编码说明符。

5 设备特定数据

  除了公共属性之外,还可以向节点添加任意属性和子节点。只要遵循一些规则,就可以添加操作系统所需的任何数据。

  第一,新的特定于设备的属性名称应使用制造商前缀,以便它们不会与现有的标准属性名称冲突。

  第二,必须在绑定中记录属性和子节点的含义,以便设备驱动程序作者知道如何解释数据。绑定文档描述特定兼容值的含义,它应具有的属性,可能具有的子节点以及它所代表的设备。每个唯一的兼容值应具有自己的绑定(或声称与另一个兼容值的兼容性)。此Wiki中记录了新设备的绑定。有关文档格式和审阅过程的说明,请参见主页。

  第三,在devicetree-discuss@lists.ozlabs.org邮件列表上发布新的绑定以供审核。检查新绑定会捕获许多常见错误,这些错误将在未来引发问题。

6 特殊节点

6.1  aliases 节点

  特定节点通常由完整路径引用,例如/ external-bus/ethernet @ 0,0,但是当用户真正想知道的是“哪个设备是eth0?”时,这会很麻烦。 别名节点可用于为完整设备路径分配短别名。 例如:

    aliases {
        ethernet0 = &eth0;
        serial0 = &serial0;
    };

  建议操作系统在为设备分配标识符时使用别名。

  你会注意到这里使用的新语法。 属性=&label; 语法将标签引用的完整节点路径指定为字符串属性。 这与phandle = <&label>不同; 之前使用的表单,将一个phandle值插入一个单元格。

6.2 chosen 节点

    chosen 节点不代表真实设备,而是用作在固件和操作系统之间传递数据的地方,如引导参数。 所选节点中的数据不代表硬件。 通常,  chosen 节点在.dts源文件中保留为空,并在引导时填充。

在我们的示例系统中,固件可能会将以下内容添加到所选节点:

    chosen {
        bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
    };

7 进阶知识

7.1 高级样机

  现在我们已经定义了基础知识,让我们在样本机器上添加一些硬件来讨论一些更复杂的用例。

  高级示例计算机添加了一个PCI主桥,其控制寄存器映射到0x10180000,而BAR则编程为在地址0x80000000之上启动。

  鉴于我们已经了解的设备树,我们可以从添加以下节点开始描述PCI主桥。

        pci@10180000 {
            compatible = "arm,versatile-pci-hostbridge", "pci";
            reg = <0x10180000 0x1000>;
            interrupts = <8 0>;
        };

7.2 PCI主桥

  本节介绍主机/ PCI桥接节点。

  注意,本节假设了一些PCI的基本知识。 这不是关于PCI的教程,如果您需要更深入的信息,请阅读[1]。 您还可以参考ePAPR v1.1或 PCI Bus Binding to Open Firmware。 可在此处找到飞思卡尔MPC5200的完整工作示例。

7.2.1 PCI总线编号

  每个PCI总线段都是唯一编号的,并且总线编号通过使用包含两个单元的bus-ranges属性在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地址转换

  与前面描述的本地总线类似,PCI地址空间与CPU地址空间完全分离,因此需要进行地址转换才能从PCI地址转换到CPU地址。 与往常一样,这是使用范围,#address-cells和#size-cells属性完成的。

        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>;
        };

  如您所见,子地址(PCI地址)使用3个单元,PCI范围编码为2个单元。 第一个问题可能是,为什么我们需要三个32位单元来指定PCI地址。 这三个细胞标记为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 bit位存储空间
  • 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。

为了使扳手投入工作,phys.hi位域的存在意味着操作系统需要知道该节点代表PCI桥,以便它可以忽略不相关的字段以进行转换。操作系统将在PCI总线节点中查找字符串“pci”,以确定是否需要屏蔽额外字段。

7.2.3 PCI DMA地址转换

  上述范围定义了CPU如何查看PCI内存,并帮助CPU设置正确的内存窗口并将正确的参数写入各种PCI设备寄存器。这有时称为出站内存。

  地址转换的一个特例涉及PCI主机硬件如何看待系统的核心内存。当PCI主控制器将充当主控制器并独立访问系统的核心存储器时,会发生这种情况。由于这通常与CPU的视图不同(由于存储器线的连接方式),因此可能需要在初始化时将其编程到PCI主控制器中。这被视为一种DMA,因为PCI总线独立地执行直接存储器访问,因此映射被命名为dma-ranges。这种类型的内存映射有时称为入站内存,不是PCI设备树规范的一部分。

  在某些情况下,ROM(BIOS)或类似设备将在启动时设置这些寄存器,但在其他情况下,PCI控制器完全未初始化,并且需要从设备树设置这些转换。然后,PCI主机驱动程序通常会解析dma-ranges属性并相应地在主机控制器中设置一些寄存器。

  扩展上面的示例:

        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-ranges条目表示从PCI主机控制器的角度来看,PCI地址0x00000000处的512 MB将出现在主核心存储器中的地址0x80000000处。 如您所见,我们只是将ss地址类型设置为0x02,表明这是一些32位内存。

7.3 高级中断映射

  现在我们来到最有趣的部分,PCI中断映射。 PCI设备可以使用电线#INTA,#INTB,#INTC和#INTD触发中断。单功能设备有义务使用#INTA进行中断。如果多功能设备使用单个中断引脚,则必须使用#INTA,如果使用两个中断引脚,则必须使用#INB,等等。由于这些规则,#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中断号只使用一个单元,这与使用2个单元的系统中断控制器不同;一个用于irq号码,一个用于旗帜。 PCI仅需要一个单元用于中断,因为PCI中断被指定为始终是低电平敏感的。

在我们的示例板中,我们有2个PCI插槽,分别有4个中断线,因此我们必须将8个中断线映射到中断控制器。这是使用interrupt-map属性完成的。中断映射的确切过程在[3]中描述。

  由于中断号(#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-map-mask也是一个像元素中断说明符一样的4元组。 掩码中的1表示应考虑单元中断说明符的哪一部分。 在我们的例子中,我们可以看到只需要phys.hi的设备号部分,我们需要3位来区分四条中断线(计数PCI中断线从1开始,而不是0!)。

  现在我们可以构造interrupt-map属性。 此属性是一个表,该表中的每个条目都包含一个子(PCI总线)单元中断说明符,一个父句柄(负责提供中断的中断控制器)和一个父单元中断说明符。 因此,在第一行中,我们可以读到PCI中断#INTA映射到IRQ 9,我们的中断控制器是低电平的。  [4]。

  目前唯一缺少的部分是PCI总线单元中断说明符中的奇怪数字。 单元中断说明符的重要部分是来自phys.hi位字段的设备编号。 设备编号是特定于板的,它取决于每个PCI主控制器如何激活每个设备上的IDSEL引脚。 在该示例中,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.

  将所有内容放在一起显示中断映射属性:

  • #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

  和

  • #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>; property描述了主机/ PCI桥控制器本身可能触发的中断。 不要将这些中断与PCI设备可能触发的中断混淆(使用INTA,INTB,...)。

  最后要注意的一件事。 与interrupt-parent属性一样,节点上存在中断映射属性将更改所有子节点和孙节点的默认中断控制器。 在此PCI示例中,这意味着PCI主桥成为默认的中断控制器。 如果通过PCI总线连接的设备与另一个中断控制器直接连接,那么它还需要指定自己的中断父支柱

8 备注

  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.
posted @ 2018-09-18 15:04  fred2018  阅读(859)  评论(0编辑  收藏  举报