【设备树使用】-- 4 进阶

5 设备特定数据

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

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

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

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

6 特殊节点

6.1 aliases节点

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

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

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

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

6.2 chosen 节点

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

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

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

7 高级主题

7.1 高级样品机

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

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

扫描二维码关注公众号,回复: 3392145 查看本文章

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

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

7.2 PCI主桥

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

注意,本节假设了一些PCI的基本知识。 这不是关于PCI的教程,如果您需要更深入的信息,请阅读[1]。 您还可以参考 ePAPR v1.1PCI Bus Binding to Open Firmware。可在 here 找到飞思卡尔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-ranges = <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-ranges = <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: prefetchable(可缓存)区域标志
  • 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位可预取的存储区,从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-ranges = <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情况是目前最常见的。

            compatible = "arm,versatile-pci-hostbridge", "pci";
            reg = <0x10180000 0x1000>;
            interrupts = <8 0>;
            bus-ranges = <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,我们的中断控制器是低电平的。 

目前唯一缺少的部分是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。

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

  • 插槽1的#INTA是IRQ9,在主中断控制器上是低电平敏感的
  • 插槽1的#INTB是IRQ10,在主中断控制器上是低电平敏感的
  • 插槽1的#INTC是IRQ11,在主中断控制器上是低电平敏感的
  • 插槽1的#INTD是IRQ12,在主中断控制器上是低电平敏感的

  • 插槽2的#INTA是IRQ10,在主中断控制器上是低电平敏感的
  • 插槽2的#INTB是IRQ11,在主中断控制器上是低电平敏感的
  • 插槽2的#INTC是IRQ12,在主中断控制器上是低电平敏感的
  • 插槽2的#INTD是IRQ9,在主中断控制器上是低电平敏感的

interrupts = <8 0>; 描述了主机/ PCI桥控制器本身可能触发的中断。不要将这些中断与PCI设备可能触发的中断混淆(使用INTA,INTB,...)。

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

原文: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/81016754