1.简介
在Linux内核中设备树中,定义了一系列属性,用来描述PCIe总线。比如"bus-range"属性,描述PCIe某个domain的总线编号范围,比如"ranges"属性,描述PCIe地址转换。下面将分别介绍这些属性。
2.设备类型
由于PCIe总线有一些特有的属性,需要在驱动初始化的时候内核自动解析。因此需要在设备树定义设备类型,如下所示。当设备类型为"pci"
时,内核就知道这是一个PCI Host Brage。
[arch/arm64/boot/dts/rockchip/rk3588.dtsi]
pcie3x4: pcie@fe150000 {
......
device_type = "pci";
......
}
Linux内核调用__of_node_is_type
函数解析设备类型。
[drivers/of/base.c]
static bool __of_node_is_type(const struct device_node *np, const char *type)
{
const char *match = __of_get_property(np, "device_type", NULL);
return np && match && type && !strcmp(match, type);
}
3.PCI域
在Linux内核中,一个PCI设备,通常使用domain number:bus number:device number.function number(比如0000:00:00.0)描述。domain number表示PCI域的编号,bus number表示总线编号,device number表示设备编号,function number表示功能编号,bus number、device number和function number也称之为BDF。PCI域用来给PCI Host Brage编号,有几个PCI Host Brage,就有几个PCI域。通常情况下,各个PCI域之间不能直接通信。在设备树中,PCI域使用"linux,pci-domain"
属性描述。
[arch/arm64/boot/dts/rockchip/rk3588.dtsi]
pcie3x4: pcie@fe150000 {
......
linux,pci-domain = <0>;
......
}
Linux内核调用of_get_pci_domain_nr
函数解析PCI域编号。
[drivers/pci/of.c]
int of_get_pci_domain_nr(struct device_node *node)
{
u32 domain;
int error;
error = of_property_read_u32(node, "linux,pci-domain", &domain);
if (error)
return error;
return (u16)domain;
}
4.速度和Link Width
PCIe总线的最大速度由"max-link-speed"
属性描述,Link Width由"num-lanes"
描述。真实的速度和Link Width由RC、PCIe桥和EP协商决定。
[arch/arm64/boot/dts/rockchip/rk3588.dtsi]
pcie3x4: pcie@fe150000 {
......
max-link-speed = <3>;
num-lanes = <4>;
......
}
Linux内核中可使用下面的接口解析"max-link-speed"
和"num-lanes"
属性。
[drivers/pci/of.c]
int of_pci_get_max_link_speed(struct device_node *node)
{
u32 max_link_speed;
if (of_property_read_u32(node, "max-link-speed", &max_link_speed) ||
max_link_speed == 0 || max_link_speed > 4)
return -EINVAL;
return max_link_speed;
}
[drivers/pci/controller/pcie-rockchip.c]
err = of_property_read_u32(node, "num-lanes", &rockchip->lanes);
5.ranges
"ranges"
属性定义了CPU地址到PCI地址的转换关系(outbound memory)。在PCI设备枚举的时候,PCI主机会根据ranges属性,设置对应的memory region,同时将PCI地址设置到PCI设备的寄存器中,当枚举完成后,CPU可以直接通过地址访问PCI设备。"dma-ranges"
属性则与"ranges"
属性相反,定义PCI地址到CPU地址的转换关系(inbound memory)。
"#address-cells"
定于使用几个cell描述PCI地址,"#size-cells"
定义使用几个cell描述地址长度。因此"ranges"
属性和"dma-ranges"
属性描述的地址意义如下所示。
[arch/arm64/boot/dts/rockchip/rk3588.dtsi]
pcie3x4: pcie@fe150000 {
......
#address-cells = <3>
#size-cells = <2>;
/* PCI地址标志 PCI地址高32位 PCI地址低32位 CPU地址高32位 CPU地址低32位 地址长度高32位 地址长度低32位 */
ranges = < 0x00000800 0x0 0xf0000000 0x0 0xf0000000 0x0 0x100000
0x81000000 0x0 0xf0100000 0x0 0xf0100000 0x0 0x100000
0x82000000 0x0 0xf0200000 0x0 0xf0200000 0x0 0xe00000
0xc3000000 0x9 0x00000000 0x9 0x00000000 0x0 0x40000000>;
......
};
[arch/arm64/boot/dts/renesas/r8a774b1.dtsi]
pciec0: pcie@fe000000 {
......
#address-cells = <3>;
#size-cells = <2>;
/* Map all possible DDR as inbound ranges */
/* PCI地址标志 PCI地址高32位 PCI地址低32位 CPU地址高32位 CPU地址低32位 地址长度高32位 地址长度低32位 */
dma-ranges = <0x42000000 0 0x40000000 0 0x40000000 0 0x80000000>;
......
};
PCI地址标志由8部分组成,其二进制位域的意义如下所示,PCIe总线中,只有pss
有意义。
npt000ss bbbbbbbb dddddfff rrrrrrrr
n: relocatable region flag (doesn't play a role here)
p: prefetchable (cacheable) region flag
t: aliased address flag (doesn't play a role here)
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 may be structured hierarchically.
So we may have PCI/PCI bridges which will define sub busses.
ddddd: The device number, typically associated with IDSEL signal connections.
fff: The function number. Used for multifunction PCI devices.
rrrrrrrr: Register number; used for configuration cycles.
因此pcie3x4节点中"ranges"
属性定义的4个地址段意义如下:
- 一个配置空间,从PCI地址0xf0000000开始,大小为1MB,将映射到Host CPU的0xf0000000地址处,具体的地址根据访问设备的BDF动态映射。
- 一个IO空间,从PCI地址0xf0100000开始,大小为1MB,将映射到Host CPU的0xf0100000地址处。
- 一个32位非预取内存空间,从PCI地址0xf0200000开始,大小为14MB,将映射到Host CPU的0xf0200000地址处。
- 一个64位预取内存空间,从PCI地址0x900000000开始,大小为14GB,将映射到Host CPU的0x900000000地址处。
因此pciec0节点中"dma-ranges"
属性定义的地址段意义如下:
- 从Host CPU的角度看,一个32位非预取内存空间,从PCI地址0x40000000开始,大小为2GB,将映射到Host CPU内存0x40000000地址处。这样设置后,EP的DMA可以直接访问Host CPU的内存。
Linux内核中使用下面定义的函数解析"ranges"
属性和"dma-ranges"
属性。其中of_bus_pci_match
匹配PCI总线,of_bus_pci_get_flags
解析PCI地址标志。
[drivers/of/address.c]
static struct of_bus of_busses[] = {
#ifdef CONFIG_PCI
/* PCI */
{
.name = "pci",
.addresses = "assigned-addresses",
.match = of_bus_pci_match,
.count_cells = of_bus_pci_count_cells,
.map = of_bus_pci_map,
.translate = of_bus_pci_translate,
.has_flags = true,
.get_flags = of_bus_pci_get_flags,
},
#endif /* CONFIG_PCI */
......
};
6.中断
PCIe总线中,涉及INTx、MSI、MSI-X中断。INTx中断需要在设备树中配置映射关系。MSI和MSI-X中断需要配置和ITS的映射关系。
6.1.INTx中断
由于很多PCI设备只使用INTA中断,若中断很频繁时,这些中断都将集中到INTA上,导致中断效率降低。因此需要将PCI插槽上的INTx中断以旋转(swizzling)方式连接到中断控制器上的不同中断引脚上。设备树需要一种将每个PCI中断信号映射到中断控制器输入的方法。"#interrupt-cells"
、"interrupt-map"
和"interrupt-map-mask"
属性用于描述中断映射。"#interrupt-cells"
属性表示描述中断需要几个cell,"interrupt-map-mask"
表示PCI插槽(包含了device id信息)和INTx中断编号的掩码,"interrupt-map"
表示INTx和中断控制器的映射关系。
如下图所示,phys.hi phys.mid phys.low
分别表示PCI插槽位置信息,由于在PCIe总线中,INTx中断使用消息机制实现,不需要中断引脚,因此PCI插槽位置信息可以设置为0。INTx对应的一列分别表示INTA、INTB、INTC、INTD中断。"interrupt-map-mask"
和"interrupt-map"
的前4列相与得到最终的PCI插槽位置信息和INTx类型。因此下面分别将INTA、INTB、INTC、INTD映射到pcie3x4_intc中断控制器的0、1、2、3号中断上,实质上pcie3x4_intc是一个虚拟的中断控制器,父中断控制器为GIC,其使用260号中断向GIC提交中断。
[arch/arm64/boot/dts/rockchip/rk3588.dtsi]
pcie3x4: pcie@fe150000 {
......
interrupts = <GIC_SPI 263 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 262 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 261 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 260 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 259 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "sys", "pmc", "msg", "legacy", "err";
#interrupt-cells = <1>;
/* phys.hi phys.mid phys.low INTx */
interrupt-map-mask = < 0 0 0 7>;
/* phys.hi phys.mid phys.low INTx 映射的中断控制器 中断编号 */
interrupt-map = <0 0 0 1 &pcie3x4_intc 0>, /* INTA */
<0 0 0 2 &pcie3x4_intc 1>, /* INTB */
<0 0 0 3 &pcie3x4_intc 2>, /* INTC */
<0 0 0 4 &pcie3x4_intc 3>; /* INTD */
......
pcie3x4_intc: legacy-interrupt-controller {
interrupt-controller;
#address-cells = <0>;
#interrupt-cells = <1>;
interrupt-parent = <&gic>;
interrupts = <GIC_SPI 260 IRQ_TYPE_EDGE_RISING>;
};
......
}
6.2.MSI和MSI-X中断
GICv3及以上版本实现了ITS(Interrupt Translation Service)。因此在ARM架构上,可基于ITS实现MSI或MSI-X中断。设备树使用"msi-map"
属性描述MSI或MSI-X中断和msi-controller的映射关系。"msi-map"
属性的第一个数据表示MSI Data,即MSI中断向量起始编号,需要配置到PCIe设备的配置空间中,第二个数据引用msi-controller节点,msi-controller位于gic节点内,第三个数据表示PCIe设备起始16位的Requester ID(BDF),第四个数据表示中断数量,下面申请了4096个中断。
[arch/arm64/boot/dts/rockchip/rk3588.dtsi]
pcie3x4: pcie@fe150000 {
......
/* MSI Data msi-controller Requester ID length */
msi-map = <0x0000 &its1 0x0000 0x1000>;
......
};
[arch/arm64/boot/dts/rockchip/rk3588s.dtsi]
gic: interrupt-controller@fe600000 {
compatible = "arm,gic-v3";
#interrupt-cells = <3>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
interrupt-controller;
reg = <0x0 0xfe600000 0 0x10000>, /* GICD */
<0x0 0xfe680000 0 0x100000>; /* GICR */
interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
its0: msi-controller@fe640000 {
compatible = "arm,gic-v3-its";
msi-controller;
#msi-cells = <1>;
reg = <0x0 0xfe640000 0x0 0x20000>;
};
its1: msi-controller@fe660000 {
compatible = "arm,gic-v3-its";
msi-controller;
#msi-cells = <1>;
reg = <0x0 0xfe660000 0x0 0x20000>;
};
};
参考资料
- PCIEXPRESS体系结构导读
- PCI Express technology 3.0
- PCI Express® Base Specification Revision 5.0 Version 1.0
- Rockchip RK3588 TRM
- https://elinux.org/Device_Tree_Usage#PCI_Host_Bridge