linux 设备树dts基础


活动地址:CSDN21天学习挑战赛

学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您:
想系统/深入学习某技术知识点…
一个人摸索学习很难坚持,想组团高效学习…
想写博客但无从下手,急需写作干货注入能量…
热爱写作,愿意让自己成为更好的人…

本篇文章是刚加入到rk3399的第一天学习内容,学习linux的设备树知识,本文所写的知识点原来网页为:

Device Tree Usage - eLinux.org 

内容为翻译稿件加上自己的理解。

(此页面之前位于 http://devicetree.org/Device_Tree_Usage)

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

有关设备树数据格式的完整技术描述,请参阅 ePAPR v1.1 规范。 ePAPR 规范涵盖了比本页涵盖的基本主题更多的细节,请参阅本页未涵盖的更高级用法。 ePAPR 目前正在更新为 Devicetree 规范文档的新名称。

内容
1 基本数据格式
2 基本概念
2.1 样品机
2.2 初始结构
2.3 CPU
2.4 节点名称
2.5 设备
2.6 理解 compatible 属性
3 寻址的工作原理
3.1 CPU寻址
3.2 内存映射设备
3.3 非内存映射设备
3.4 范围(地址转换)
4 中断是如何工作的
5 设备特定数据
6个特殊节点
6.1 别名节点
6.2 选择节点
7个高级主题
7.1 先进的样品机
7.2 PCI 主机桥
7.2.1 PCI 总线编号
7.2.2 PCI 地址转换
7.2.3 PCI DMA 地址转换
7.3 高级中断映射
8 笔记

基本数据格式
设备树是节点和属性的简单树结构。 属性是键值对,节点可能同时包含属性和子节点。 例如,以下是 .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”
一堆散布在树上的属性。
属性是简单的键值对,其中值可以为空或包含任意字节流。虽然数据类型未编码到数据结构中,但有一些基本数据表示可以在设备树源文件中表示。

文本字符串(以空结尾)用双引号表示:
string-property = "一个字符串";
'Cells' 是由尖括号分隔的 32 位无符号整数:
细胞属性 = <0xbeef 123 0xabcd1234>;
二进制数据用方括号分隔:
二进制属性 = [0x01 0x23 0x45 0x67];
不同表示的数据可以使用逗号连接在一起:
混合属性=“字符串”,[0x01 0x23 0x45 0x67],<0x12345678>;
逗号也用于创建字符串列表:
string-list = "红鱼", "蓝鱼";

基本概念
要了解设备树是如何使用的,我们将从一个简单的机器开始,并建立一个设备树来逐步描述它。

样品机
考虑以下由“Acme”制造并命名为“Coyote's Revenge”的假想机器(大致基于 ARM Versatile):

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

初始结构
第一步是为机器铺设骨架结构。 这是有效设备树所需的最低限度的结构。 在这个阶段,您想要唯一地识别机器。 

/dts-v1/;

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

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

从理论上讲,兼容是操作系统唯一标识机器所需的所有数据。 如果所有机器细节都是硬编码的,那么操作系统可以在顶级兼容属性中专门寻找“acme,coyotes-revenge”。

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节点添加更多属性,但我们首先需要谈谈更多的基本概念。

节点名称
值得花点时间谈谈命名约定。 每个节点必须具有 <name>[@<unit-address>] 形式的名称。

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

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

兄弟节点必须唯一命名,但只要地址不同(即,serial@101f1000 & serial@101f2000),多个节点使用相同的通用名称是正常的。

有关节点命名的完整详细信息,请参阅 ePAPR 规范的第 2.2.1 节。

 设备
系统中的每个设备都由一个设备树节点表示。 下一步是使用每个设备的节点填充树。 目前,新节点将保持空白,直到我们可以讨论如何处理地址范围和 irq。 

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

在这棵树中,为系统中的每个设备添加了一个节点,层次结构反映了设备如何连接到系统。 IE。 extern 总线上的设备是外部总线节点的子节点,而 i2c 设备是 i2c 总线控制器节点的子节点。 一般来说,层次结构代表了从 CPU 角度看系统的视图。

这棵树此时无效。 它缺少有关设备之间连接的信息。 稍后将添加该数据。

在这棵树中需要注意的一些事项:

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

理解 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”或类似的值。芯片供应商总是会做出改变,打破你的通配符假设,一旦改变为时已晚。相反,选择一个特定的硅实现并使所有后续的硅与它兼容。

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

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

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

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

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,表示没有大小字段的地址。 在这种情况下,两个 cpu 被分配了地址 0 和 1。对于 cpu 节点,#size-cells 为 0,因为每个 cpu 只分配了一个地址。

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

内存映射设备
与在 cpu 节点中发现的单个地址值不同,内存映射设备被分配了一系列地址,它将响应。 #size-cells 用于说明每个子 reg 元组中长度字段的大小。 在下面的示例中,每个地址值是 1 个单元(32 位),每个长度值也是 1 个单元,这在 32 位系统上很典型。 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>;
        };
    };

外部总线使用 2 个单元作为地址值; 一个用于片选编号,一个用于从片选基数偏移。 长度字段保留为单个单元格,因为只有地址的偏移部分需要有一个范围。 因此,在此示例中,每个 reg 条目包含 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>;
            };
        };

范围(地址转换)
我们已经讨论了如何为设备分配地址,但此时这些地址仅对设备节点是本地的。 它还没有描述如何从这些地址映射到 CPU 可以使用的地址。

根节点总是描述 CPU 对地址空间的看法。 根的子节点已经在使用 CPU 的地址域,因此不需要任何显式映射。 例如,serial@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 设备。缺少范围属性意味着设备不能被除父设备以外的任何设备直接访问。

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

中断控制器 - 将节点声明为接收中断信号的设备的空属性
#interrupt-cells - 这是中断控制器节点的属性。它说明此中断控制器的中断说明符中有多少个单元(类似于#address-cells 和#size-cells)。
interrupt-parent - 设备节点的属性,包含指向它所连接的中断控制器的 phandle。没有interrupt-parent 属性的节点也可以从其父节点继承该属性。
中断 - 包含中断说明符列表的设备节点的属性,一个用于设备上的每个中断输出信号。
中断说明符是一个或多个数据单元(由#interrupt-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-cells 为 2,因此每个中断说明符有 2 个单元。 此示例使用常见模式,即使用第一个单元对中断线编号进行编码,使用第二个单元对标志进行编码,例如高电平有效与低电平有效,或边沿与电平敏感。 对于任何给定的中断控制器,请参阅控制器的绑定文档以了解说明符是如何编码的。

设备特定数据
除了通用属性之外,还可以将任意属性和子节点添加到节点中。只要遵循一些规则,操作系统需要的任何数据都可以添加。

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

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

第三,在 [email protected] 邮件列表上发布新的绑定以供审核。审查新的绑定会发现很多常见的错误,这些错误会在未来引起问题。

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

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

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

您会注意到这里使用了一种新语法。 属性 = &label; 语法将标签引用的完整节点路径分配为字符串属性。 这和 phandle = < &label >; 不同。 前面使用的表单,它将一个 phandle 值插入到单元格中。

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

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

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

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

高级示例机器添加了一个 PCI 主机桥,其控制寄存器内存映射到 0x10180000,并且 BAR 被编程为从地址 0x80000000 以上开始。

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

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

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

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

PCI 总线编号
每个 PCI 总线段都有唯一的编号,总线编号通过使用 bus-range 属性在 pci 节点中公开,该属性包含两个单元格。 第一个单元给出分配给该节点的总线号,第二个单元给出任何从属 PCI 总线的最大总线号。

示例机器有一个 pci 总线,所以两个单元格都是 0。

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

PCI 地址转换
与前面描述的本地总线类似,PCI 地址空间与 CPU 地址空间完全分离,因此需要进行地址转换才能从 PCI 地址到 CPU 地址。 与往常一样,这是使用 range、#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位内存空间
bbbbbbbb:PCI 总线编号。 PCI 可以是分层结构的。 所以我们可能有定义子总线的 PCI/PCI 桥。
ddddd:设备编号,通常与 IDSEL 信号连接相关联。
fff:功能编号。 用于多功能 PCI 设备。
rrrrrrrr:寄存器号; 用于配置周期。

或者PCI地址转换的目的,重要的字段是p和ss。 phys.hi 中 p 和 ss 的值决定了正在访问的 PCI 地址空间。因此,查看我们的范围属性,我们有三个区域:

一个 32 位可预取内存区域,从 PCI 地址 0x80000000 开始,大小为 512 MB,将映射到主机 CPU 上的地址 0x80000000。
一个 32 位不可预取的内存区域,从 PCI 地址 0xa0000000 开始,大小为 256 MB,将映射到主机 CPU 上的地址 0xa0000000。
一个 I/O 区域,从 PCI 地址 0x00000000 开始,大小为 16 MB,将映射到主机 CPU 上的地址 0xb0000000。
为了让工作更加投入,phys.hi 位域的存在意味着操作系统需要知道该节点代表一个 PCI 桥,以便它可以忽略不相关的字段以进行翻译。操作系统将在 PCI 总线节点中查找字符串“pci”以确定它是否需要屏蔽额外字段。

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 位内存。

高级中断映射
现在我们来到最有趣的部分,PCI 中断映射。 PCI 设备可以使用#INTA、#INTB、#INTC 和#INTD 线触发中断。中断名称前面的#井号表示它是低电平有效,这是一个常见的约定,PCI 中断线总是低电平有效。单功能设备必须使用#INTA 进行中断。如果多功能设备使用单个中断引脚,则必须使用#INTA,如果使用两个中断引脚,则必须使用#INTA 和#INTB 等。由于这些规则,#INTA 通常用于比#INTB、#INTC 更多的功能和#INTD。为了在支持#INTA 到#INTD 的四个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 个单元的系统中断控制器不同;一个用于中断号,一个用于标志。 PCI 只需要一个单元用于中断,因为 PCI 中断被指定为始终对低电平敏感。

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

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

由 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!)。

现在我们可以构造中断映射属性。该属性是一个表,该表中的每个条目由一个子(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 部分来确定的,如下所示:

插槽 1 的 phys.hi 为 0xC000,并且
插槽 2 的 phys.hi 为 0xC800。
将它们放在一起,中断映射属性显示:

#INTA 的 slot 1 是 IRQ9,对主中断控制器低电平敏感
#INTB 的 slot 1 是 IRQ10,对主中断控制器低电平敏感
#slot 1的INTC是IRQ11,对主中断控制器低电平敏感
#INTD of slot 1 is IRQ12, level lowsensitive on the primary interrupt controller

#INTA of slot 2 is IRQ10, level lowsensitive on the primary interrupt controller
#INTB 的 slot 2 是 IRQ11,对主中断控制器低电平敏感
#INTC 的 slot 2 是 IRQ12,对主中断控制器低电平敏感
#INTD of slot 2 is IRQ9, level low sensitive on the primary interrupt controller
中断 = <8 0>;属性描述了主机/PCI 桥控制器本身可能触发的中断。不要将这些中断与 PCI 设备可能触发的中断混为一谈(使用 INTA、INTB、...)。

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

猜你喜欢

转载自blog.csdn.net/weixin_41579872/article/details/126114806