设备树参考文档(Device Tree Usage)

设备树参考文档(Device Tree Usage)

0 官方文档

https://elinux.org/Device_Tree_Reference 官方手册
https://elinux.org/Device_Tree_Usage 入门示例(本文主要从此处翻译)

1 基本结构

设备树是一个树形结构,由节点和相关的属性组成。属性是键值对(key-val)结构;节点既包含属性,也包含子节点,举例来说,一个简单的.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 {
        };
    };
};

这棵树包含下面的内容:

  • 根节点:/
  • 一组子节点:node1node2
  • 一组node1的子节点:child-node1child-node2
  • 分布在树节点上的一些属性

属性是简单的键值对,其中的值可以为空,也可以包含任意字节流。有一些基本的数据类型可以在设备树源文件中表示。

  • 字符串 string,以null结尾,由双引号包裹
    string-property = "a string";
  • 元组 cell,包含32位无符号整型数,由尖括号包裹
    cell-property = <0xbeef 123 0xabcd1234>;
  • 二进制数据 Binary,由方括号包裹
    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 Versatile),由“Acme”制造,命名为“Coyote’s Revenge”:

  • 一个32位ARM CPU
  • 处理器本地总线连接到内存映射的串行端口、spi总线控制器、i2c控制器、中断控制器、和外部总线桥
  • 1个256MB的SDRAM,基地址为0
  • 2个地址分别为0x101F1000和0x101F2000的串口
  • 1个GPIO控制器,地址为0x101F3000
  • 1个地址为0x10170000的SPI控制器,包含以下设备
    • 带有SS引脚的MMC插槽,连接到GPIO #1
  • 外部总线桥,带有下列设备
    • SMC SMC91111以太网设备,连接到地址为0x10100000的外部总线
    • 地址为0x10160000的i2c控制器,包含以下设备
      • Maxim DS1338实时时钟,对应从属地址1101000 (0x58)
    • 64MB的NOR flash,地址为0x30000000

2.2 初始结构

第一步是为机器设计一个骨架结构。这是有效设备树所需的最小结构。在这个阶段,希望唯一地标识机器。

/dts-v1/;

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

compatible指定系统的名称。它包含一个“<manufacturer>,<model>”形式的字符串。重要的是要指定确切的设备,并包括制造商名称,以避免名称空间冲突。由于操作系统将使用compatible值来决定如何在机器上运行,因此将正确的数据放入该属性非常重要。

理论上,compatible是操作系统唯一标识一台机器所需的所有数据。如果所有的机器细节都是硬编码的,那么操作系统可以在顶层com中专门查找“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";
        };
    };
};

同样可以用“<manufacturer>,<model>”的形式指定确切的cpu型号。
稍后将向cpu节点添加更多属性,但我们首先需要讨论更多基本概念。

2.4 节点名

每个节点必须具有<name> [@<unit-address>]格式的名称。

<name> 是一个简单的 ascii 字符串,最长可达31个字符。一般来说,节点是根据它所代表的设备类型来命名的。也就是说。3com以太网适配器的节点应该使用ethernet,而不是3com509

如果节点描述具有地址的设备,则包含@<unit-address>。通常,单元地址是用于访问设备的主地址,并在节点的reg属性中列出。稍后我们将在本文档中介绍reg属性。

兄弟节点(即同一级节点下的不同节点)必须是唯一命名的,但是对于多个节点来说,只要地址不同,使用相同的通用名称是正常的(例如,series@101f1000 & series@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";
        };
    };
};

在此树中,为系统中的每个设备添加了一个节点,层次结构反映了设备的连接情况。

例中,外部总线上的设备是外部总线节点external-bus的子节点,i2c设备是i2c总线控制节点的子节点。一般来说,层次结构从CPU的角度展示系统视图,即CPU是如何看待这个节点挂在那个总线/设备下的。

这个树在这一点上是无效的。它缺少关于设备之间连接的信息。这些数据将在稍后添加。

注意:

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

2.6 理解compatible属性

树中表示设备的每个节点都必须具有compatible属性,其用于决定将哪个设备驱动程序绑定到具体设备。

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

例如,Freescale MPC8349 SoC有一个串行器件,实现了National Semiconductor ns16550寄存器接口。因此,MPC8349串行设备的兼容属性应该是:compatible = "fsl,mpc8349-uart","ns16550"。在这种情况下,fsl,mpc8349-uart指定了确切的设备,ns16550表示它与National Semiconductor 16550 UART兼容。

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

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

警告:不要使用通配兼容的符号,如“fsl,mpc83xx-uart”或类似的值。硅供应商总是会做出改变,打破你的通配符假设,而这时改变已经太晚了。相反,选择一个特定的芯片实现,并使所有后续芯片与之兼容。

3 地址

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

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

每个可寻址设备有一个reg,这是一个tuple列表,形式为reg = <address1 length1 [address2 length2] [address3 length3] … >。每个addresslength为一对tuple,表示设备使用的地址范围。每个address是一个或多个32位整数的列表,表示为一个或多个cell。同样,length可以是一个或多个cell,也可以为空。

由于addresslength字段都是可变大小的变量,因此使用父节点中的#address-cells#size-cells属性来声明每个字段中有多少个单元格。或者换句话说,正确解释reg属性需要父节点的#address-cells#size-cells值。为了了解这一切是如何工作的,让我们将寻址属性添加到示例设备树中,从cpu开始。

3.1 CPU地址

在讨论寻址时,CPU节点代表了最简单的情况。每个CPU被分配一个唯一的ID,并且size与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>;
        };
    };

在cpu节点中,#address-cells设置为1,#size-cells设置为0。这意味着reg值是一个单独的uint32。

在本例中,两个cpu分别分配了地址0和1。对于CPU节点,#size-cells为0,因为每个CPU只分配地址不分配地址范围。

注意到 reg 值与节点名称中的值相匹配。按照约定,如果节点具有 reg 属性,则节点名必须包含 unit-address,即 reg 属性中的第一个 address 值。

3.2 内存映射设备

不同于在cpu节点中讨论的单个地址值,内存映射设备被分配了一个地址范围,它关联这些地址。

#size-cells用于声明每个reg元组中的长度字段有多大。在下面的示例中,每个address是1个cell,每个length也是1个cell,这在32位系统中是非常普遍的。64位机器可以将#address-cells#size-cells的值设置为2来在设备树中获得64位寻址(1个cell是32位,32+32获得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设备地址被分配了两个地址范围;0 x101f3000…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个cell作为地址值;一个用于芯片编号的选择,另一个用于芯片偏移量的选择。length字段为1个cell,因为只有地址的偏移部分需要有一个范围。因此,在本例中,每个reg包含3个cell,芯片编号,偏移量和地址长度。

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

注意:这里并没有展示cpu是如何将0 0或1 0这种地址编号+偏移的映射转换为cpu可以理解的地址空间,后续会讲到。

3.3 非内存映射设备

其他设备在处理器总线上没有内存映射。它们可以有地址范围,但是它们不能被CPU直接访问。相反,父设备的驱动程序将执行间接访问。

以i2c设备为例,rtc设备都分配了一个地址,但没有与之关联的长度或范围。这看起来与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 的地址域,因此不需要任何显式映射。例如,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是地址转换的列表。ranges表中的每个条目都是一个tuple,包含子地址、父地址和子地址空间中区域的大小。每个字段的大小由子字段的 #address-cells值、父字段的 #address-cells 值和子字段的 #size-cells 值确定。对于我们示例中的外部总线,子地址是2个cell,父地址是1个cell,size也是1个cell。目前正在转换三个范围:

  • 芯片0偏移0映射到地址范围0x10100000…0x1010ffff
  • 芯片1偏移0映射到地址范围0x10160000…0x1016ffff
  • 芯片2偏移0映射到地址范围0x30000000…0x30ffffff

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

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

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

4 中断

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

  • interrupt-controller:空属性,声明一个节点是接收中断信号的设备
  • #interrupt-cells:中断控制器节点的属性。它声明这个中断控制器的中断标识符中有多少个cell(类似于#address-cells#size-cells)。
  • interrupt-parent:设备节点的属性,它包含一个连接到中断控制器的句柄。没有interrupt-parent属性的节点也可以从它们的父节点继承该属性。
  • interrupts:设备节点的属性,包含中断标识符(interrupt specifier)的列表,设备上的每个中断输出信号对应一个中断标识符。

interrupt specifier是一个或多个cell组成的数据(由#interrupt-cells指定),它指定设备连接到哪个中断输入。大多数设备只有一个中断输出,如下面的例子所示。一个设备上有多个中断输出也是可行的。interrupt specifier的含义完全取决于中断控制器设备的绑定。每个中断控制器可以决定需要多少个cell来唯一地定义一个中断输入。

下面的代码将中断连接添加到 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>;
        };
    };
};

上述中断内容的解释:

  • 这台机器有一个中断控制器,interrupt-controller@10140000。
  • 标签intc:已经添加到中断控制器节点,并且该标签用于为根节点中的中断父属性分配一个句柄。此中断父值成为系统的默认值,因为除非显式重写,否则所有子节点都将继承它。
  • 每个设备使用一个中断属性来指定不同的中断输入线。
  • #interrupt-cells为2,所以每个中断标识符有2个单元格。这个例子使用的常见模版是,使用第一个cell表示中断行号,使用第二个cell表示flag,如高/低电平,或是边缘/水平触发等。对于任何给定的中断控制器,请参考控制器的文档来了解是如何编码的。

5 其他特定数据

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

首先,新的特定于设备的属性名应该使用制造商前缀,这样它们就不会与现有的标准属性名冲突。

其次,必须在绑定的文档中记录属性和子节点的含义,以便设备驱动程序开发者知道如何解释数据。文档记录了特定兼容值的含义、它应该具有的属性、它可能具有的子节点以及它表示的设备。每个唯一compatible值都应该有自己的绑定(或声明与另一个兼容值兼容)。有关文档格式和评审过程的描述,请参见主页

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

6 特殊节点

6.1 aliases节点

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

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

注意到这里使用了一种新的语法。property = &label; 语法将label引用的完整节点路径分配为字符串属性。这不同于前面使用的 phandle = <&label>,后者将一个 phandle 值插入到cell中。

6.2 chosen节点

所选节点并不代表真正的设备,而是作为在固件和操作系统之间传递数据的地方,比如引导参数。所选节点中的数据不代表硬件。通常,所选节点在.dts源文件中为空,并在引导时填充。
在我们的示例系统中,固件可能会向所选节点添加以下内容:

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

7 高级主题

7.1 高级样机

现在我们已经定义了基础,让我们向示例机器添加一些硬件,以讨论一些更复杂的用例。

下面的机器添加了一个PCI host bridge,其中控制寄存器内存映射到0x10180000,并将BARs被规划到地址0x80000000上启动。

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

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

7.2 PCI Host Bridge

介绍Host/PCI桥接节点。
注意,本节假定有一些PCI的基本知识。这不是一个关于PCI的教程,如果你需要更深入的信息,请阅读[1]。您也可以参考ePAPR v1.1或PCI总线绑定到开放固件。

7.2.1 PCI总线编号

每个 PCI 总线段都是唯一编号的,并且通过使用bus-range属性(包含两个cell)在 PCI 节点中公开总线编号。第一个cell给出分配给这个节点的总线号,第二个cell给出从属 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地址。与往常一样,这是通过ranges#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>;
        };

这里,子地址使用3个cell(前三个cell),PCI范围使用2个cell(后两个cell)。

第一个问题可能是,为什么我们需要三个32位cell来指定PCI地址。这三个cell被标记为 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位宽,并被编码为 phy.mid 和 phy.low。然而,真正有趣的事情发生在 phy.high 中,它是一个 bit 字段:

  • n: 可重定位的区域标志(在这里不起作用)
  • p: 预取(缓存)区域标志
  • t: 别名地址标志(在这里不起作用)
  • ss:空间类型
    • 00: 配置空间
    • 01: I/O空间
    • 10: 32位内存空间
    • 11: 64位内存空间
  • bbbbbbbb:PCI 总线编号。PCI 可能是分层结构的。因此,我们可能会有 PCI/PCI 桥接器来定义子总线。
  • ddddd: 设备号,通常与 IDSEL 信号连接相关联。
  • fff: 函数号。用于多功能 PCI 设备。
  • rrrrrrrr: 寄存器编号; 用于配置周期。

在PCI地址转换中,重要的字段是psspss在phys.hi中的值确定正在访问哪个PCI地址空间。看一下ranges属性,我们有三个区域:

  • 一个32位的可预取内存区域,从PCI地址0x80000000开始,大小为512 MByte,将映射到主机CPU上的地址0x80000000。
  • 一个32位不可预取的内存区域,从PCI地址0xa0000000开始,256 MByte大小,将映射到主机CPU上的地址0xa0000000。
  • 一个从PCI地址0x00000000开始的16 MByte大小的I/O区域,它将映射到主机CPU上的地址0xb0000000。

phys.hi字段的存在意味着操作系统需要知道该节点代表PCI桥,这样它就可以忽略不相关的字段进行翻译。操作系统将在pci总线节点中查找字符串“pci”,以确定是否需要屏蔽额外字段。

7.2.3 PCI DMA地址翻译

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

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

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

扩展上面的例子:

        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线触发中断。中断名称前面的# hash符号表示它处于低电平活动状态,这是一种常见的惯例,PCI中断线总是处于低电平活动状态。单一功能的设备必须使用#INTA进行中断。多功能设备如果使用单个中断引脚,则必须使用#INTA,如果使用两个中断引脚,则必须使用#INTA和#INTB,等等。由于这些规则,相比于#INTB、#INTC和#INTD,#INTA通常会被更多的函数使用。为了通过将负载分配到#INTA到#INTD的四条IRQ线上,每个PCI插槽或设备通常以轮转(原文是rotating)的方式连接到中断控制器上的不同输入,以避免所有#INTA客户端连接到同一条传入中断线上。这个过程被称为对中断打乱(swizzling)。因此,设备树需要一种将每个PCI中断信号映射到中断控制器输入的方法。#interrupt-cells, interrupt-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中断号只使用一个cell,不像系统中断控制器使用2个cell那样一个是irq编号,另一个是flag。PCI只需要一个cell用于中断,因为PCI中断被指定为始终是低电平敏感的。

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

因为中断号(#INTA等)不足以区分单个PCI总线上的几个PCI设备,还必须表示哪个PCI设备触发了中断线。幸运的是,每个PCI设备都有一个唯一的设备号,我们可以使用它。为了区分多个PCI设备的中断,我们需要一个由PCI设备号和PCI中断号组成的元组tuple。更一般地说,我们构造一个有四个cell的unit中断标识符:

  • 三个#address-cells,包括phys.hi, phys.mid, phys.low
  • 一个##interrupt-cell,为(#INTA, #INTB, #INTC, #INTD)之一

因为我们只需要PCI地址的设备号部分,interrupt-map-mask属性开始发挥作用。interrupt-map-mask也是一个4元组,类似于unit中断标识符。掩码中的第一个cell表示应该考虑unit中断标识符的哪一部分。在例子中,可以看到只有设备编号部分的phys.hi是必需的,我们需要3位来区分四条中断线(计数PCI中断线从1开始,而不是从0开始!)。

现在我们可以构造interrupt-map属性。该属性的每个行由子PCI总线的unit中断标识符(占用4cell),父句柄(负责服务中断的中断控制器,占用1cell)和父unit中断标识符(占2cell)组成。因此,在第一行中,我们可以读到PCI中断#INTA被映射到中断控制器的低电平触水平触发的irq9上[4]

目前唯一缺少的部分是PCI总线unit中断标识符中的奇怪数字。uint中断标识符的重要部分是来自phys.hi的bit字段。设备号是板特定的,它取决于每个PCI主机控制器如何激活每个设备上的IDSEL引脚。在此例中,为PCI slot 1分配设备id 24 (0x18),为PCI slot 2分配设备id 25 (0x19)。每个slotphys.hi通过将设备号左移11位到bit位的ddddd部分来确定,如下所示:

  • 0x18左移11位为0xC000
  • 0x19左移11位为0xC800

把所有这些放在一起,interrupt-map属性表示:

  • slot 1的 #INTA 是 IRQ9,主中断控制器上的低电平水平触发
  • slot 1的 #INTB 是 IRQ10,主中断控制器上的低电平水平触发
  • slot 1的 #INTC 是 IRQ11,主中断控制器上的低电平水平触发
  • slot 1的 #INTD 是 IRQ12,主中断控制器上的低电平水平触发

以及

  • slot 2的 #INTA 是 IRQ10,主中断控制器上的低电平水平触发
  • slot 2的 #INTB 是 IRQ11,主中断控制器上的低电平水平触发
  • slot 2的 #INTC 是 IRQ12,主中断控制器上的低电平水平触发
  • slot 2的 #INTD 是 IRQ9,主中断控制器上的低电平水平触发

interrupts = <8 0>;属性描述了host/PCI-bridge控制器本身可能触发的中断。不要将这些中断与可能触发的中断PCI device混淆(使用 INTA、 INTB、 …)。

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

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
设备树Device Tree)是一种用于描述硬件设备及其配置的数据结构格式,它被广泛应用于嵌入式系统中。设备树使用手册主要包括设备树的概述、语法和用法等方面的内容。 设备树的概述部分介绍了设备树的作用和原理,它可以描述系统中的硬件设备以及它们之间的连接关系,使操作系统在启动过程中能够准确识别和配置硬件设备,从而提升系统的兼容性和可移植性。 设备树的语法部分详细介绍了设备树的数据结构和格式。设备树使用一种基于节点和属性的层次结构来表示设备和设备之间的关系。每个节点表示一个硬件设备或一个设备的功能模块,节点之间通过属性来描述它们之间的连接和配置关系。 设备树的用法部分介绍了如何在实际开发中使用设备树。它包括编写设备树源文件、编译设备树、加载设备树等步骤。通过编写设备树源文件,开发者可以描述系统中的硬件设备及其配置信息,然后通过编译和加载设备树,操作系统可以根据设备树的描述来识别和配置硬件设备。 设备树使用手册还可以进一步介绍设备树的工具和技术,例如设备树编辑器、设备树编译器、设备树绑定等。这些工具和技术能够帮助开发者更方便地编写、编译和验证设备树。此外,设备树使用手册还可以提供一些实际应用案例和开发经验,帮助开发者快速上手和解决实际问题。 总结来说,设备树使用手册是用于指导开发者如何使用设备树来描述和配置硬件设备的指南。它包括设备树的概述、语法和用法等内容,并提供了相关工具和技术以及实际应用案例。通过学习和理解设备树使用手册,开发者能够更好地使用设备树来进行嵌入式系统开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值