引言
PCIe的全称是Peripheral Component Interconnect Express,是一种用于连接外设的总线标准。自2003年提出以来,它作为替代PCI和PCI-X的方案,已成为现代CPU和几乎所有外设交互的标准或基石。诸如GPU、网卡、USB控制器、声卡等,在我们日常所使用的设备中,几乎都是通过PCIe总线连接的。此外,现在基于m.2接口的SSD,采用NVMe协议,也通过PCIe总线进行连接。而Thunderbolt 3、USB4,甚至最新的CXL互联协议,都是基于PCIe的!
CXL(Compute Express Link)是业界支持的高速缓存一致性互连协议,用于处理器、内存扩展和加速器之间的通信。CXL技术确保CPU内存空间和附加设备上的内存一致性,从而允许资源共享,提高性能,减少软件堆栈的复杂性,并降低整体系统成本。
因此,一旦涉及设备相关的开发,PCIe几乎成为不可或缺的一环。最近,我研究了一些与PCIe相关的资料,这里简要总结一下,并希望能对大家有所帮助。本文主要关注硬件方面,与操作系统本身关系不大,无论是Windows还是Linux,底层部分都非常相似。文章还会涉及调试方法,尤其是以Linux为主。
那我们就开始吧!
1. PCIe总体框图
首先,我们先从PCIe的基本概念开始。PCIe的架构主要由五个部分组成:
-
Root Complex, -
PCIe Bus, -
Endpoint, -
Port and Bridge, -
Switch。
其整体架构呈现一个树状结构,如下图所示:
2. Root Complex(RC)
Root Complex是整个PCIe设备树的根节点,CPU通过它与PCIe的总线相连,并最终连接到所有的PCIe设备上。
主板上最重要、成本最高的两颗芯片,被称为北桥和南桥,其中北桥负责与处理器对接,主要功能包括:内存控制器、PCI-E控制器、集成显卡、前/后端总线等,都是速度较快的模块;而南桥则负责外围周边功能,速度较慢,主要包括:磁盘控制器、网络端口、扩展卡槽、音频模块、I/O接口等等。
Intel与AMD的新一代处理器,已经将传统北桥的大部分功能都整合在了CPU内部,Intel的Clarkdale与Sandybridge处理器则是完全整合北桥芯片,与其搭配的P55/H55/P67/H67等芯片组其实就是一颗南桥。
Clarkdale的北桥(GPU)和CPU部分示意图
CPU部分和GPU部分是各自独立的,微观上通过QPI总线相连,宏观上被封装在了一起,接口是与Lynnfield相同的LGA1156。整体上来看Clarkdale不仅整合了内存控制器和PCI-E控制器,还整合了显示核心,看似更加先进。
实际上,Clarkdale只是将原本放在主板上的北桥芯片,挪到了CPU的铁盖下面,本质上并没有整合任何东西(包括显卡和内存控制器)。但是与之搭配的H55芯片组,确实只剩下了一颗南桥。北桥的发热量远高于南桥,由于北桥位于处理器上面,因此用户再也不用担心主板的散热问题了。
目前市面上热卖的Core i7 8XX和Core i5 7XX处理器,就是基于Lynnfield核心的产品,这是真正意义上整合了北桥的处理器。
用图片小结
FSB总线:即前端总线(Front Side Bus),CPU和北桥之间的桥梁,CPU和北桥传递的所有数据必须经过FSB总线,可以这么说FSB总线的频率直接影响到CPU访问内存的速度。 北桥:北桥是CPU和内存、显卡等部件进行数据交换的唯一桥梁,也就是说CPU想和其他任何部分通信必须经过北桥。北桥芯片中通常集成的还有内存控制器等,用来控制与内存的通信。现在的主板上已经看不到北桥了,它的功能已经被集成到CPU当中了。 PCI总线:PCI总线是一种高性能局部总线,其不受CPU限制,构成了CPU和外设之间的高速通道。比如现在的显卡一般都是用的PCI插槽,PCI总线传输速度快,能够很好地让显卡和CPU进行数据交换。 南桥:主要负责I/O设备之间的通信,CPU要想访问外设必须经过南桥芯片。
回正题
由于Root Complex是管理外部IO设备的,所以在早期的CPU上,Root Complex其实是放在了北桥(MCU)上 [5],后来随着技术的发展,现在已经都集成进了CPU内部了 [8]。(注意下图的System Agent的部分,他就是PCIe Root Complex所在的位置。)
另外,虽然是根节点,但是系统里面可以存在不只一个Root Complex。随着PCIe Lane的增加,PCIe控制器和Root Complex的数量也随之增加。比如,我的台式机的CPU是i9-10980xe,上面就有4个Root Complex,而我的笔记本是i7-9750H,上面就只有一个Root Complex。我们在Windows上可以通过设备管理器来查看:
Linux上也类似。下图是从我的服务器的主板说明书上截出来的框图,用的CPU是EPYC 7742,可以很明显的看到PEG P0-3,对应着4个PCIe Controller和Root Complex:[6]
而我们可以通过lspci命令来查看所有的Root Complex:
$ lspci -t -v
-+-[0000:c0]-+-00.0 Advanced Micro Devices, Inc. [AMD] Starship/Matisse Root Complex
+-[0000:80]-+-00.0 Advanced Micro Devices, Inc. [AMD] Starship/Matisse Root Complex
+-[0000:40]-+-00.0 Advanced Micro Devices, Inc. [AMD] Starship/Matisse Root Complex
\-[0000:00]-+-00.0 Advanced Micro Devices, Inc. [AMD] Starship/Matisse Root Complex
3. PCIe总线(Bus)
PCIe上的设备通过PCIe总线互相连接。虽然PCIe是从PCI发展而来的,并且甚至有很多地方是兼容的,但是它与老式的PCI和PCI-X有两点特别重要的不同:
PCIe的总线并不是我们传统意义上共享线路的总线(Bus),而是一个点对点的网络,我们如果把PCI比喻成网络中的集线器(Hub),那么PCIe对应的就是交换机了。换句话说,当Root Complex或者PCIe上的设备之间需要通信的时候,它们会与对方直接连接或者通过交换电路进行点对点的信号传输。[7]
PCI Bus
PCIe Bus
老式的PCI使用的是单端并行信号进行连接,但是由于干扰过大导致频率无法提升,所以后来就演变成PCIe之后就开始使用了高速串行信号。这也导致了PCI设备和PCIe设备无法兼容,只能通过PCI-PCIe桥接器来进行连接。当然这些我们都不需要再去关心了,因为现在已经很少看见PCI的设备了。
关于PCIe的通讯和包路由交换,我们先到这里,后面会更深入的介绍。
4. PCIe Device
PCIe上连接的设备可以分为两种类型:
-
Type 0:它表示一个PCIe上最终端的设备,比如我们常见的显卡,声卡,网卡等等。 -
Type 1:它表示一个PCIe Switch或者Root Port。和终端设备不同,它的主要作用是用来连接其他的PCIe设备,其中PCIe的Switch和网络中的交换机类似。
4.1. BDF(Bus Number, Device Number, Function Number)
PCIe上所有的设备,无论是Type 0还是Type 1,在系统启动的时候,都会被分配一个唯一的地址,它有三个部分组成:
-
Bus Number:8 bits,也就是最多256条总线 -
Device Number:5 bits,也就是最多32个设备 -
Function Number:3 bits,也就是最多8个功能
这就是我们常说的BDF,它类似于网络中的IP地址,一般写作BB:DD.F的格式。在Linux上,我们可以通过lspci命令来查看每个设备的BDF,比如,下面这个FCH SMBus Controller就是00:14.0:
$ lspci -t -v
# [Domain:Bus]
\-[0000:00]-+-00.0 Advanced Micro Devices, Inc. [AMD] Starship/Matisse Root Complex
# Device.Function
+-14.0 Advanced Micro Devices, Inc. [AMD] FCH SMBus Controller
在我们知道了任何一个设备的BDF之后,我们就可以通过它查看到这个设备的详细信息了,如下:
$ lspci -s 00:14.0 -vv
00:14.0 SMBus: Advanced Micro Devices, Inc. [AMD] FCH SMBus Controller (rev 61)
Subsystem: Super Micro Computer Inc H12SSL-i
Control: I/O+ Mem+ BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx+
Status: Cap- 66MHz+ UDF- FastB2B- ParErr- DEVSEL=medium >TAbort- SERR- in use: piix4_smbus
Kernel modules: i2c_piix4, sp5100_tco
另外,由于默认BDF的方式最多只支持8个Function,可能不够用,所以PCIe还支持另一种解析方式,叫做ARI(Alternative Routing-ID Interpretation),它将Device Number和Function Number合并为一个8bit的字段,只用于表示Function,所以最多可以支持256个Function,但是它是可选的,需要通过设备配置启用 [1]。
4.2. Type 0 Device和Endpoint
所有连接到PCIe总线上的Type 0设备(终端设备),都可以来实现PCIe的Endpoint,用来发起或者接收PCIe的请求和消息。每个设备可以实现一个或者多个Endpoint,每个Endpoint都对应着一个特定的功能。比如:
-
一块双网口的网卡,可以每个为每个网口实现一个单独的Endpoint; -
一块显卡,其中实现了4个Endpoint:一个显卡本身的Endpoint,一个Audio Endpoint,一个USB Endpoint,一个UCSI Endpoint; 这些我们都可以通过lspci或者Windows上的设备管理器来查看:
$ lspci -t -v
-+-[0000:c0]-+-00.0 Advanced Micro Devices, Inc. [AMD] Starship/Matisse Root Complex
# A NIC card with 2 ports:
| +-01.1-[c1]--+-00.0 Mellanox Technologies MT2892 Family [ConnectX-6 Dx]
| | \-00.1 Mellanox Technologies MT2892 Family [ConnectX-6 Dx]
+-[0000:80]-+-00.0 Advanced Micro Devices, Inc. [AMD] Starship/Matisse Root Complex
# A graphic card with 4 endpoints:
| +-01.1-[81]--+-00.0 NVIDIA Corporation TU104 [GeForce RTX 2080]
| | +-00.1 NVIDIA Corporation TU104 HD Audio Controller
| | +-00.2 NVIDIA Corporation TU104 USB 3.1 Host Controller
| | \-00.3 NVIDIA Corporation TU104 USB Type-C UCSI Controller
4.3. RCIE(Root Complex Integrated Endpoint)
说到PCIe设备,脑海里面可能第一反应就是有一个PCIe的插槽,然后把显卡或者其他设备插在里面,就像我们上面看到的这样。但是其实系统中有大量的设备是主板上集成好了的,比如,内存控制器,集成显卡,Ethernet网卡,声卡,USB控制器等等。
这些设备在连接PCIe的时候,可以直接连接到Root Complex上面。这种设备就叫做RCIE(Root Complex Integrated Endpoint),如果我们去查看的话,他们的Bus Number都是0,代表Root Complex。
4.4. Port / Bridge
那么其他的需要通过插槽连接的设备呢?这些设备就需要通过PCIe Port来连接了。
在Root Complex上,有很多的Root Port,这些Port每一个都可以连接一个PCIe设备(Type 0或者Type 1)。
本质上,所有这些连接其他设备用的部件都是由桥(Bridge)来实现的,这些桥的两端连接着两个不同的PCIe Bus(Bus Number不同)。
比如,一个Root Port其实是靠两个Bridge来实现的:一个(共享的)Host Bridge(上游连接着CPU,下游连接着Bus 0)和一个PCI Bridge用来连接下游设备(上游连着的是Bus 0(Root Complex),下游连着的PCIe的设备(Bus Number在启动过程中自动分配)) [1]。
我们通过lspci命令可以看到这些桥的存在(注意设备详情中的Kernel driver in use: pcieport):
+-[0000:80]-+-00.0 Advanced Micro Devices, Inc. [AMD] Starship/Matisse Root Complex
# This is the Host bridge that connects to the root port and CPU:
| +-01.0 Advanced Micro Devices, Inc. [AMD] Starship/Matisse PCIe Dummy Host Bridge
# This is the PCI bridge that connects to the root port and device with a new bus - 0x81:
| +-01.1-[81]--+-00.0 NVIDIA Corporation TU104 [GeForce RTX 2080]
| | +-00.1 NVIDIA Corporation TU104 HD Audio Controller
| | +-00.2 NVIDIA Corporation TU104 USB 3.1 Host Controller
| | \-00.3 NVIDIA Corporation TU104 USB Type-C UCSI Controller
# Host bridge
$ sudo lspci -s 80:01.0 -v
80:01.0 Host bridge: Advanced Micro Devices, Inc. [AMD] Starship/Matisse PCIe Dummy Host Bridge
Flags: fast devsel, IOMMU group 13
# PCI bridge
$ sudo lspci -s 80:01.1 -v
80:01.1 PCI bridge: Advanced Micro Devices, Inc. [AMD] Starship/Matisse GPP Bridge (prog-if 00 [Normal decode])
Flags: bus master, fast devsel, latency 0, IRQ 35, IOMMU group 13
Bus: primary=80, secondary=81, subordinate=81, sec-latency=0
I/O behind bridge: 0000b000-0000bfff [size=4K]
Memory behind bridge: f0000000-f10fffff [size=17M]
Prefetchable memory behind bridge: 0000020030000000-00000200420fffff [size=289M]
....
Kernel driver in use: pcieport
注意:是否使用PCIe Bridge和是否通过插槽连接不能直接划等号,这取决于你系统的硬件实现,比如,从上面RCIE的截图中我们可以看到USB Controller作为RCIE存在,而下面EPYC的CPU则不同,USB控制器是通过Root Port连接的,但是它在主板上并没有插槽。
$ lspci -t -v
+-[0000:40]-+-00.0 Advanced Micro Devices, Inc. [AMD] Starship/Matisse Root Complex
+-03.0 Advanced Micro Devices, Inc. [AMD] Starship/Matisse PCIe Dummy Host Bridge
| +-03.3-[42]----00.0 ASMedia Technology Inc. ASM1042A USB 3.0 Host Controller
# ^====== 40:03.3 here is a Bridge. And USB controller is connected
# to this Bridge with a new Bus Number 42.
4.5. Switch
如果我们需要连接不止一个设备怎么办呢?这时候就需要用到PCIe Switch了。
PCIe Switch内部主要有三个部分:
-
一个Upstream Port和Bridge:用于连接到上游的Port,比如,Root Port或者上游Switch的Downstream Port -
一组Downstream Port和Bridge:用于连接下游的设备,比如,显卡,网卡,或者下游Switch的Upstream Port -
一根虚拟总线:用于将上游和下游的所有端口连接起来,这样,上游的Port就可以访问下游的设备了
另外,这里再说明一次 —— 由于PCIe的信号传输是点对点的,所以Switch中间的这个总线只是一个逻辑上的虚拟的总线,其实并不存在,里面真正的结构是一套用于转发的交换电路 [9]。
最后,看到这里也许你会突然想到Root Complex是不是也可以看成是一个Switch呢?我觉得这两个概念最好还是分开,虽然从很多框图上看着确实很像,只不过Root Complex没有Upstream Port,连接上游的Host Bridge是连接到CPU上,不过Root Complex内部的功能要远比Switch复杂的多,里面不仅仅是简单的包转发,比如,后面会说到的PCIe请求的生成和转换等等。
5. 小结
好了,到这里我们已经将PCIe设备树中的主要部件都介绍完毕了。如果我们把所有这些部件连接在一起,那么其整体的结构就是这样的 [10]:
好的,为了避免文章过长,我们这一篇就先到这里,后面有时间再继续总结和PCIe相关的其他知识,比如配置空间和域,消息和消息路由等等。
PCIe系统举例
-
PCIe拓扑特征:图的顶部是一个CPU。这里要说明的一点是,CPU被认为是PCle层次结构的顶层。PCle只允许简单的树结构,这意味着不允许循环或其他复杂的拓扑结构。这样做是为了保持与PCI软件的向后兼容性,PCI软件使用一个简单的配置方案来跟踪拓扑,不支持复杂的环境。为了保持这种兼容性,软件必须能够与以前相同的方式生成配置周期,总线拓扑也必须与以前相同。因此,软件期望找到的所有配置寄存器仍然在那里,并且以它们始终具有的方式运行。
在上图PCIe系统中有几种设备类型,Root Complex、Switch、Bridge、Endpoint等,下面分别介绍其概念。
-
Root Complex:简称RC,CPU和PCle总线之间的接口,可能包含几个组件(处理器接口、DRAM接口等),甚至可能包含几个芯片。RC位于PCI倒立树拓扑的“根”,并代表CPU与系统的其余部分进行通信。但是,规范并没有仔细定义它,而是给出了必需和可选功能的列表。从广义上讲,RC可以理解为系统CPU和PCle拓扑之间的接口,PCle端口在配置空间中被标记为“根端口”。 -
Bridge:桥提供了与其他总线(如PCI或PCI- x,甚至是另一个PCle总线)的接口。如图中显示的桥接有时被称为“转发桥接”,它允许旧的PCI或PCIX卡插入新系统。相反的类型或“反向桥接”允许一个新的PCle卡插入一个旧的PCI系统。 -
Switch:提供扩展或聚合能力,并允许更多的设备连接到一个PCle端口。它们充当包路由器,根据地址或其他路由信息识别给定包需要走哪条路径。是一种PCIe转PCIe的桥。 -
Endpoint:处于PCIe总线系统拓扑结构中的最末端,一般作为总线操作的发起者(initiator,类似于PCI总线中的主机)或者终结者(Completers,类似于PCI总线中的从机)。显然,Endpoint只能接受来自上级拓扑的数据包或者向上级拓扑发送数据包。细分Endpoint类型的话,分为Lagacy PCIe Endpoint和Native PCIe Endpoint,Lagacy PCIe Endpoint是指那些原本准备设计为PCI-X总线接口的设备,但是却被改为PCIe接口的设备。而Native PCIe Endpoint则是标准的PCIe设备。其中,Lagacy PCIe Endpoint可以使用一些在Native PCIe Endpoint禁止使用的操作,如IO Space和Locked Request等。Native PCIe Endpoint则全部通过Memory Map来进行操作,因此,Native PCIe Endpoint也被称为Memory Mapped Devices(MMIO Devices)
如上图,是一个高端服务器系统,系统内建其他组网接口,如FC,ETH,SAS/SATA等。
“Intel Processor”包含许多组件,大多数现代CPU架构都是如此。这一个包括一个PCle端口访问图形,和2个DRAM通道,这意味着内存控制器和一些路由逻辑已经集成到CPU中。这些资源通常统称为“Uncore”逻辑,将它们与包中的几个CPU内核及其关联逻辑区分开来。
Root Complex描述为CPU和PCle拓扑之间的接口,这意味着该部分必须位于CPU中。
以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !