Home 设备树PCI相关
Post
Cancel

设备树PCI相关

设备树用法

PCI相关示例

样例使用 PCI Host Bridge(PCIe RC),像在xilinx的Zynq UltraScale+,Versal平台就有使用。 这里的示例可以参考内核源码的versatile-pb.dts设备树文件。

这里补充一个后面调试过的xilinx的pcie rc的设备树节点,(一些值是根据硬件设置的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    amba_pl: amba_pl@0 {
            #address-cells = <2>;
            #size-cells = <2>;
            compatible = "simple-bus";
            ranges ;

            ......

            pcie_dma_versal_0: axi-pcie@a4000000 {
                    #address-cells = <3>;
                    #interrupt-cells = <1>;
                    #size-cells = <2>;
                    compatible = "xlnx,qdma-host-3.00";
                    device_type = "pci";
                    interrupt-map = <0 0 0 1 &psv_pcie_intc_0 1>, <0 0 0 2 &psv_pcie_intc_0 2>, <0 0 0 3 &psv_pcie_intc_0 3>, <0 0 0 4 &psv_pcie_intc_0 4>;
                    interrupt-map-mask = <0 0 0 7>;
                    interrupt-names = "misc", "msi0", "msi1";
                    interrupt-parent = <&gic>;
                    interrupts = <0 84 4 0 85 4 0 86 4>;
                    ranges = <0x02000000 0x00000000 0xa5000000 0x0 0xa5000000 0x00000000 0x400000>;
                    reg = <0x0 0xa4000000 0x0 0x40000>,
                            <0x0 0xa6000000 0x0 0x400000>;
                    reg-names = "breg", "cfg";
                    psv_pcie_intc_0: interrupt-controller {
                            #address-cells = <0>;
                            #interrupt-cells = <1>;
                            interrupt-controller ;
                    };
            };

            ......

    };

PCI Bus numbering

PCI总线号(Bus Number)在系统中是唯一的,在pci rc节点中使用bus-range 属性表示,它包含两个cell。 第一个cell表示分配给该节点的总线号,第二个cell表示该总线的从属总线的最大总线数量。

以下示例中,总线号为0,最大数量也是0。也就是这个rc上就只能接一个ep设备,没有switch,这在嵌入式环境或许是常见的,一个rc只挂一个ep?

1
2
3
4
5
6
        pci@0x10180000 {
            compatible = "arm,versatile-pci-hostbridge", "pci";
            reg = <0x10180000 0x1000>;
            interrupts = <8 0>;
            bus-range = <0 0>;
        };

PCI Address Translation

PCI地址转换,因为PCI总线地址和CPU地址空间的各自独立的,需要将PCI总线地址转换为CPU地址,才能给CPU访问。在x86上,一般都是一一映射的, 所以有点感觉不到,嵌入式环境也可以一一映射。与往常一样,这是通过range, #address-cells和#size-cells属性完成的。

1
2
3
4
5
6
7
8
9
10
11
12
        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个cell来表示地址。父节点用1个cell,size是2个cell。子节点的3个cell中,后面2个cell组成64位PCI总线地址(高32位加低32位),第1个cell其实是一个标记位域。 一共有32位,东西较多。将这个32位数表示为 npt000ss bbbbbbbb dddddfff rrrrrrrr:

  • n: relocatable region flag ,可重定位区域标志,
  • p: prefetchable (cacheable) region flag ,可预取(可缓存)标志,
  • t: aliased address flag ,别名地址标志,
  • ss: space code, 00: configuration space, 01: I/O space, 10: 32 bit memory space, 11: 64 bit memory space,
  • bbbbbbbb: The PCI bus number. 总线号,因为PCI总线上可以扩展出PCI桥,这样会有子总线号。
  • ddddd: The device number, 设备号,typically associated with IDSEL signal connections.
  • fff: The function number,功能号
  • rrrrrrrr: Register number; used for configuration cycles.注册号;用于配置周期。

其中比较重要的位域是pss域。pci总线地址标记位域的存在,意味着系统需要知道这是一个PCI桥设备,以便它可以忽略不相关的字段以进行转换。 操作系统将在pci总线节点中查找字符串“pci”,以确定是否需要屏蔽额外的字段。

PCI DMA Address Translation

上述的ranges定义了CPU如何查看PCI内存,并帮助CPU正确设置内存访问窗口,并写入正确参数到相关PCI设备寄存器。 这种内存映射有时称为outbound memory(出站内存,从主机内存往外的)。按个人理解,这部分主要是用于PCI设备的PCI config head部分。

还有PCI host controllor如何看待主机内存的问题。主要是PCI host controllor需要作为master,并访问主机内存的情况, 其实基本就是controllor通过DMA读写主机内存的情况。这两种情况是不同,对于PCI host controllor看待主机内存的情况, 需要在PCI host controllor初始化时设置好。针对这种情况,也有一个专门的属性dma-ranges。这种映射方式有时会叫 inbound memory(入站内存,从外往主机内存的)。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
        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 host controller的视角看,位于PCI总线地址0x00000000的512MB空间将出现在位于地址0x80000000的主机内存中。 而且,ss位域是02,说明是32 bit memory space。

Advanced Interrupt Mapping

这里主要介绍基于传统的中断线的PCI中断映射,不是x86上主流的MSI中断。经典的PCI设备使用#INTA、#INTB、#INTC和#INTD一个信号线触发中断。 #符号表示低电平为激活状态,这是一种常见的惯例,PCI中断线总是处于低电平激活状态。

在设备树中,需要一种将每个PCI中断信号映射到中断控制器输入的方法。#interrupt-cells, interrupt-map和interrupt-map-mask属性用于描述中断映射。 实际上,这里描述的中断映射并不局限于PCI总线,任何节点都可以指定复杂的中断映射,但PCI是目前最常见的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        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,而系统的中断控制器则使用两个cell(第一个是irq号,第二个是irq flags)。 PCI这种经典的INTx中断只需要一个cell,因为INTx中断总是低电平有效的,不需要额外的flag了。

在上述示例中,有两个PCI slot插槽(见注释),每个有4个中断线,这样就一共有8个中断需要映射到系统的中断控制器,这个通过interrupt-map属性完成。 因为一个PCI中断号(如#INTA)不足以区分一个PCI总线上多个PCI设备,还需要指出哪个PCI设备触发了中断线,(INTx中断线是共享的)。为了实现区分,还会补充 一个PCI设备号,因为设备号是每个PCI设备唯一的。使用PCI设备号和PCI中断线组成一个有4个cell的元组来表示具体的PCI中断:前三个cell为PCI设备号,具体格式 和上文的PCI总线地址的3个cell格式一样,最后一个cell为中断号(1234对应ABCD)。在上例中,0xc000实际设备号24,0xc800实际设备号25。 由于只需要PCI设备地址的设备号部分,interrupt-map-mask属性开始发挥作用,interrupt-map-mask也是一个4元组,效果就是掩码嘛, 上面的0xf8就是对应PCI地址的设备号部分的5个bit,后面那个7,对应中断号的掩码。掩码,与操作,屏蔽不相关的位。

现在可以理解interrupt-map属性了,该属性通常是一个列表,每个条目包含一个子节点的中断指示符,父节点使用的中断控制器的phandle句柄, 映射后的父节点的中断指示符。如上例中的第一个条目,表示Solt 1上的PCI设备的#INTA被映射到系统的中断控制器的IRQ 9上,flag为3即低电平有效。 总结格式就是 <[子节点的中断指示符] [映射的目标中断控制器句柄] [映射后的父节点的中断指示符]>

最后父节点(pci controllor)中也有一个interrupts = <8 0>;的属性描述,说明这个pci controllor自己本身也会触发中断, 而它是直接连接到系统的中断控制器的,所以它本身是不需要转换的。

最后,还需要注意,使用interrupt-map属性会改变该节点的子节点及孙子节点等的默认中断控制器。在该示例中,PCI controllor将成为这个节点上及子节点的默认 中断控制器,如果这个节点后面的子节点中有直接连接其他中断控制器,需要使用interrupt-parent属性单独指定。

参考

Open Firmware and Devicetree
DeviceTree Kernel API
Device Tree Usage
Devicetree Specification

This post is licensed under CC BY 4.0 by the author.