1.PCIe前身-PCI
PCI总线与PCIe总线最大的区别就是,PCI总线上的设备共享总线带宽,这意味着当总线上的设备数量很多时,可能会等待总线资源释放。而PCIe总线是点对点传输,每个设备都有自己专用的总线。


PCIe最重要的设计目的之一就是要向前兼容,风险最小、代价最小的方法就是维持软件的灵活性,即针对PCI设备的软件程序依旧可以运行在PCIe设备上。
2.PCIe拓扑结构

之前说过PCIe总线是专用总线,每个设备独占带宽,笔者一开始也很困惑是如何做到的。后来才知道这是因为RC或者Switch设备内部可以看做多个虚拟的PCI to PCI bridge的结构,这样就能拓展出多个总线,且每个总线上只有一个端点设备。
配置软件负责遍历识别每一个Bus、Device、Function,一个PCIe设备可能有多个Function,比如多功能多媒体卡,一个Function负责声音,一个Function负责视频,PCIe设备之间的通信最小单位就是功能。Bus number范围从1-256,Device number从1-32,Function number是1-8。整个枚举遍历的过程采用深度优先,如下图所示。

笔者之前有点想不明白为啥有的总线端点设备的dev number从0开始,比如总线3,而有的总线dev number是从1开始,比如总线8,这是因为PCI to PCI桥连接了两条总线,对于上游总线桥作为该总线上的一个普通设备(Device),占用一个Device号,对于下游总线(如果是PCI总线),桥作为该总线的主控制器(类似Root Port),其逻辑上不属于下游总线的设备列表,但负责管理下游总线。在下级总线(如Bus 1)中,Device 0的位置被隐式保留给PCI桥自身,虽然桥本身不占用下游总线的Device 0编号,但规范约定设备枚举从1开始,以避免冲突。
在整个枚举过程中配置软件还会把Primary Bus、Secondary Bus、Subordinate Bus写入桥设备,Primary Bus代表上游Bus号,Secondary Bus代表下游Bus号,Subordinate Bus代表所有下游总线里Bus号最大的值。完成枚举后的结果如下图所示。
3.地址空间
PCIe设备会被分配到四种地址空间,第一种是Memory Space,也就是我们所说的内存,如果是32位电脑最多能支持4GB的地址空间,64位理论上能够支持到16EB。第二种是I/O Space, 这是 x86 体系中的一种独立地址空间(不同于内存),通过 IN
/OUT
指令访问,地址范围通常为 16-bit(0x0000~0xFFFF),也就是最多支持64KB,早期 PCI/PCIe 设备使用 I/O Space 访问寄存器(如串口、PS/2 控制器),但现代 PCIe 设备更倾向于使用 MMIO(内存映射IO)的方式,访问速度快得多。第三种是配置空间,PCIe 设备的配置空间(Configuration Space)存储了设备的关键信息(如 Vendor ID、Device ID、BAR、中断设置等), 传统 PCI 配置机制是通过读写I/O空间的0XCF8和0XCFC(分别代表配置空间的地址和数据)的方式,现代有的PCIe设备将 PCIe 配置空间映射到 memory space,也就是MMCFG的方式。第四种是message space,它是一种特殊的通信机制,用于在设备之间传递控制和管理信息(如中断、电源管理、错误报告等),替代传统 PCI 的边带信号,减少了对引脚的依赖。


对于PCI 设备,每个Function占用的大小是256B,所以总的配置空间大小为,256Bus x 32 Device x 8 Funtion x 256B = 16MB,而PCIe设备,每个Function占用的大小是4KB,所以总的配置空间大小为256MB。
4.配置空间详解
配置空间的前256Byte,是PCI和PCIe设备都必须有的,这也是为什么PCIe能够向前兼容PCI。配置空间的地址划分如下图所示,前 64 字节是PCI Config Header,是设备初始化和管理的核心数据结构。040-100h这部分描述了设备的能力结构,用于扩展设备的高级功能(如 MSI/MSI-X、电源管理、PCIe 高级特性等)。0x100-0xFFF 的区域属于 PCIe 扩展配置空间,仅 PCIe 设备支持。

对于Header又分为两类 ,Type 0 配置头用于标识 Endpoint(端点设备) ,Type 1 配置头用于标识桥设备(Bridge),如下图所示,橙色代表了相同的部分,绿色代表不同部分。

Vendor ID:代表了厂商ID,如 0x8086
=Intel,0x10DE
=NVIDIA,可以向PCI-SIG协会申请。
Device ID:由厂商自己定义,例如43A0 对应 NVIDIA GeForce GTX 1650显卡。
Revision ID:设备修订版本号。
Class Code:设备类别,表示该设备是显卡、网卡或者存储控制器等,比如0x030000代表
VGA 显卡。
Command:控制设备行为,如启用内存访问,只能由Host来控制。
Status:描述设备状态,如是否支持 64 位寻址,是否触发中断。
Capabilities Pointer:一个指针指向了Capabilities存放的地址,Capabilities是以链表的形式保存。
Interrupt Line:传统 INTx 中断的 IRQ 号,由 BIOS 分配。
Interrupt Pin:物理中断引脚,1
=INTA#, 2
=INTB#, 3
=INTC#, 4
=INTD#。
Expansion ROM Base Address :是 PCI/PCIe 设备配置空间中的一个寄存器,用于管理和映射设备的 Option ROM(可选扩展ROM)。它的核心功能是让设备在系统启动时(如 BIOS/UEFI 阶段)加载并执行设备特定的初始化代码。
Base Address Register:用于动态分配设备所需的地址空间用于数据传输(如 DMA、寄存器读写),支持 32 位或 64 位地址,可映射到内存空间(Memory BAR)或 I/O 空间(I/O BAR),每个设备的 BAR 地址由系统统一分配,避免冲突。
该寄存器的低位决定了BAR的类型,bit0决定了该bar是IO BAR还是Memory BAR,若是memory BAR,则bit2:1声明了该BAR是32bit寻址还是64bit寻址,bit3声明了是否可预取。


设备会将BAR 寄存器的低位只读字段设置为0,系统软件会向 BAR 写入全 1
(即0xFFFF_FFFF
),再读回值,通过回读的值计算大小和类型,最后将分配好的基地址写入。一个32bit Memory类型可预取的BAR初始化过程如图所示。如果是64bit的,则会将两个32bit的BAR寄存器合并成一个64bit的,初始化过程一样。Type0可以有6个BAR寄存器,Type1最多只有2个BAR寄存器。

Type1类型的Header里有几个比较重要,Primary Bus number、Secondary Bus number、Subordinate Bus number描述了整个PCIe的拓扑结构。I/O Base/Limit,Memory Base/Limit,Prefetchable Memory Base/Limit 定义了桥设备下游的地址范围,用于管理下游设备(子总线)的地址空间分配和路由,PCI 桥会根据这些寄存器的值决定是否将事务转发到下游总线。下图是一个例子,需要注意的是,对于桥的IO base寄存器,低12bits必须为全0,Memory Base 和 Prefetchable Memory Base的低20bits必须为全0,这也决定了分配空间的最小单位。

从040-100h这部分地址描述了设备的能力结构,是以链表的形式存在,Capabilities Pointer指向了第一个能力的地址,链表形式如下所示,当指向下一个能力的地址为00时,代表当前的能力即为最后一个。

对于PCIe设备,有些Capabilities是必须存在的。
PCI Express Capability:定义其支持的 PCIe 版本、链路宽度、速率,提供链路训练(Link Training)、带宽协商、错误报告等功能。
PCI Power Management Capability:允许设备在空闲时进入低功耗状态(如 D1/D3hot),降低系统能耗。
MSI and/or MSI-X Capability:替代传统 INTx 中断,使用内存写入(Message Signaled Interrupt)代替边带信号,减少延迟和共享中断冲突。另外MSI-X 允许设备为不同事件分配独立的中断。
100h-FFFh空间代表了extended capabilities list,这是PCIe设备独有的部分,也是以链表的形式存在,若指向下一个capability的地址值为0,代表当前是最后一个。和Capabilities List的区别是,第一个capability默认从100h开始,如果100h读回为全0,则代表没有extended部分。