前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >linux 设备树详解-高级部分《Rice 学习开发》

linux 设备树详解-高级部分《Rice 学习开发》

作者头像
Rice加饭
发布2022-05-10 08:39:50
9930
发布2022-05-10 08:39:50
举报
文章被收录于专栏:Rice嵌入式

《基本概念》

高级模型机

现在,我们已经掌握了基本的定义,接下来让我们往模型机里添加一些硬件,以讨论一些更复杂的用例。

高级模型机添加了一个 PCI 主桥,其控制寄存器映射到内存0x10180000,并且 BARs 编程至以地址 0x80000000 为起始。

既然关于设备树我们已经有所了解了,那么我们就从以下所示新增加的节点来介绍 PCI 主桥。

代码语言:javascript
复制
pci@10180000 {
    compatible = "arm,versatile-pci-hostbridge", "pci";
    reg = <0x10180000 0x1000>;
    interrupts = <8 0>;
};

PCI主桥

注,本节将假定读者了解 PCI 的一些基本知识。本文并不是 PCI 教程,想要了解更深入的信息,请阅读 [1]。你也可以参考 ePAPR 或 PCI Bus Binding to Open Firmware(http://playground.sun.com/1275/bindings/pci/pci2_1.pdf)。还可以访问 http://devicetree.org/MPC5200:PCI,这里可以找到 Freescale MPC5200 的一个完整工作的例子

PCI 总线编号

每个 PCI 总线段都是唯一编号的,并且总线的编号是通过使用 bus-ranges 属性在 pci 节点中暴露出来的,这个属性有两个 cell。第一个 cell 给出分配给该节点的总线号;第二个 cell 给出任何次级 PCI 总线最大总线号。

模型机只有一个 pci 总线,所以两个 cell 都是 0。

代码语言:javascript
复制
pci@0x10180000 {
    compatible = "arm,versatile-pci-hostbridge", "pci";
    reg = <0x10180000 0x1000>;
    interrupts = <8 0>;
    bus-ranges = <0 0>;
};

PCI 地址转换

类似于前面所描述的本地总线,PCI 地址空间和 CPU 地址空间是完全分离的,所以需要一个从 PCI 地址到 CPU 地址的转换。同样,完成这样的转换将使用 ranges、#address-cells 和 #size-cells 属性。

代码语言:javascript
复制
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 个 cell,同时 PCI rangs 被编码为 2 个 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 位的,并编码进了 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 地址空间。因此,通过查找 ranges 属性,我们将得到三个区域。

  1. 以 PCI 地址 0x80000000 开始的一个 512 MByte 32 位预取存储区,该区域将映射到主机 CPU 地址 0x80000000。
  2. 以 PCI 地址 0xa0000000 开始的一个 265 MByte 32 位非预取存储区,该区域将映射到主机 CPU 地址 0xa0000000。
  3. 以 PCI 地址 0x00000000 开始的一个 16 MByte I/O 区,该区域将映射到主机 CPU 地址 0xb0000000。

为阻止这些工作,phys.hi 位域的存在就意味着操作系统必须知道该节点代表了一个 PCI 桥,这样操作系统才能为了地址转换而忽略那些不相关的字段。为了判断应该掩码哪些额外的字段,操作系统需要在 PCI 总线节点中寻找“pci”字符串。

高级中断映射

现在我们来到了最有趣的部分,PCI 中断映射。一个 PCI 设备可以使用引线 #INTA、#INTB、#INTC 和 #INTD 来触发中断。如果我们没有多功能 PCI 设备,那么设备中断必须使用 #INTA。然而,每个 PCI 插槽或设备通常会连接到中断控制器上不同的输入端。所以设备树需要一种能将各个 PCI 中断信号映射到中断控制器的途径。#interrupt-cells、interrupt-map 和 interrupt-map-mask 属性就被用来描述这个中断映射。

这里所描述的中断映射并不仅仅局限于 PCI 总线,事实上,任何节点都可以指定复杂的中断映射,但 PCI 是最常见的情况。

代码语言:javascript
复制
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>;

    #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&intc10 3
                     0xc000 0 0 3&intc11 3
                     0xc000 0 0 4&intc12 3

                     0xc800 0 0 1&intc10 3 // 2nd slot
                     0xc800 0 0 2&intc11 3
                     0xc800 0 0 3&intc12 3
                     0xc800 0 0 4&intc  9 3>;
};

首先你会发现,PCI 中断号只使用了一个 cell,不像系统中断控制器,它使用两个 cell,一个用于中断号,另一个用于标志。PCI 中断只使用了一个 cell,因为 PCI 中断确定为始终是低电平触发。

在这个示例板上,我们有 2 个分别包含 4 个中断线的 PCI 插槽,所以我们需要映射 8 个中断线到中断控制器上。这已经在 interrupt-map 属性中完成了。关于中断映射的具体步骤请参考 [3]。

因为要区分单一 PCI 总线上的若干 PCI 设备中断号(#INA 等)是不够用的,所以我们还需要指出是哪个 PCI 设备触发了中断线。幸运的是我们还可以使用每个设备所拥有的唯一设备号。为了区分这些 PCI 设备,我们需要一个元组,该元组由 PCI 设备号和 PCI 中断号组成。通俗的说,我们构造了由四个 cell 组成的设备中断指示符。

三个 #address-cells 由 phys.hi、phys.mid、phys.low 组成,然后

一个 #interrupt-cell(#INTA、#INTB、#INTC、#INTD)

因为我们只需要 PCI 地址中的设备号部分,所以 interrupt-map-mask 发挥了作用。interrupt-map-mask 也是 4 元组,就像设备中断指示符一样。掩码的第一部分指出我们应该考虑设备中断指示符中哪一部分。在本例中,我们可以看到在 phys.hi 中只需要设备号部分,另外我们还需要 3 位来区分四个中断线(PCI 中断线是从 1 开始计数的,不是 0!)。

现在。我们可以构建 interrupt-map 属性了。该属性是一个表,这个表的每一项都由一个子(PCI 总线)设备中断指示符、一个父句柄(用于中断服务的中断控制器)和一个父设备中断指示符组成。因此,在第一行中我们可以知道 PCI 中断 #INTA 将被映射到中断控制器的 IRQ 9,并且是低电平有效。[4] 目前为止,唯一没有讨论的就是 PCI 总线设备中断指示符里古怪的数字了。来自 phys.hi 位域的设备号是设备中断指示符中的重要组成部分。设备号是平台特定的,并取决于 PCI 主控制器如何激活各个设备的 IDSEL 管脚。在本例中,PCI slot 1 分配设备 id 24(0x18),PCI slot 2 分配设备 id 25(0x19)。每个 slot 的 phys.hi 值是通过将设备号左移 11 位至位域的 ddddd 段得到的,就像下面:

slot 1 的 phys.hi 就是 0xC000,并且slot 2 的 phys.hi 就是 0xC800。

把这些放在一起之后,interrupt-map 属性就显示为:

  1. 在主中断控制器上 slot 1 的 #INTA 是 IRQ9,低电平触发
  2. 在主中断控制器上 slot 1 的 #INTB 是 IRQ10,低电平触发
  3. 在主中断控制器上 slot 1 的 #INTC 是 IRQ11,低电平触发
  4. 在主中断控制器上 slot 1 的 #INTD 是 IRQ12,低电平触发
  5. 在主中断控制器上 slot 2 的 #INTA 是 IRQ10,低电平触发
  6. 在主中断控制器上 slot 2 的 #INTA 是 IRQ11,低电平触发
  7. 在主中断控制器上 slot 2 的 #INTA 是 IRQ12,低电平触发
  8. 在主中断控制器上 slot 2 的 #INTA 是 IRQ9,低电平触发

属性 interrupts = <8 0>; 描述了主控制器或 PCI 桥控制器本身有可能触发中断。不要与 PCI 设备触发的中断(使用 INTA,INTB,...)告混了。

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

内容参考资料书写。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-11-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rice 嵌入式开发技术分享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档