【设备树使用】-- 2 寻址

3 地址如何工作

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

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

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

由于地址和长度字段都是可变大小的变量,因此父节点中的#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 对于cpus 节点是0,因为每个cpus 仅被分配一个地址。

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

3.2 内存映射设备

不是像在cpu节点中找到的单个地址值,而是为内存映射设备分配一系列将响应的地址。 #size-cells 用于表示每个子reg 元组中length字段的大小。 在以下示例中,每个地址值为1个单元(32位),每个长度值也为1个单元,这在32位系统上是典型的。 对于#address-cells和#size-cells,64位机器可以使用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>;
        };
    };

外部总线使用2个单元作为地址值; 一个用于芯片选择号,一个用于从芯片选择的基极偏移。 长度字段保持为单个单元格,因为只有地址的偏移部分需要具有范围。 因此,在此示例中,每个reg条目包含3个单元格; chipselect号码,偏移量和长度。

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

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 范围(地址转换)

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

根节点始终描述CPU的地址空间视图。 根节点的子节点已经在使用CPU的地址域,因此不需要任何显式映射。 例如,串行@ 101f0000设备直接分配地址0x101f0000。

不是根的直接子节点的节点不使用CPU的地址域。 为了获得内存映射地址,设备树必须指定如何将地址从一个域转换为另一个域。 范围属性用于此目的。

以下是添加了范围属性的示例设备树。

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

范围是地址转换的列表。范围表中的每个条目都是一个元组,其中包含子地址,父地址和子地址空间中区域的大小。每个字段的大小取决于孩子的#address-cells值,父母的#address-cells值和孩子的#size-cells值。对于我们示例中的外部总线,子地址是2个单元,父地址是1个单元,大小也是1个单元。翻译三个范围:

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

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

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

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

原文:https://elinux.org/Device_Tree_Usage

其他关于设备树的教程:

[1] A Tutorial on the Device Tree (Zynq) -- Part I ,http://xillybus.com/tutorials/device-tree-zynq-1

[2] A Tutorial on the Device Tree (Zynq) -- Part II,http://xillybus.com/tutorials/device-tree-zynq-2

[3] A Tutorial on the Device Tree (Zynq) -- Part III,http://xillybus.com/tutorials/device-tree-zynq-3

[4] A Tutorial on the Device Tree (Zynq) -- Part IV,http://xillybus.com/tutorials/device-tree-zynq-4

[5] A Tutorial on the Device Tree (Zynq) -- Part V,http://xillybus.com/tutorials/device-tree-zynq-5

猜你喜欢

转载自blog.csdn.net/u013554213/article/details/81012710