2. PCIe总线基础
PCIe(Peripheral Component Interconnect Express)是当前主流的高速串行总线标准,它取代了传统PCI的并行共享总线模型,采用点对点、全双工的差分信号链路。理解PCIe的拓扑结构、分层协议、事务类型、配置空间与BAR机制,是编写PCIe驱动和进行内核适配的基础。本节将围绕这五个核心概念展开,帮助读者建立完整的PCIe总线认知框架。
一、PCIe拓扑结构:树形层次与点对点连接
PCIe拓扑采用树形结构,以Root Complex(RC)为根,向下通过Switch和Bridge扩展出多个端点设备(Endpoint)。与PCI的共享总线不同,PCIe的每条链路都是点对点的,设备之间不共享带宽,而是通过Switch进行路由转发。
- Root Complex:CPU与PCIe拓扑的接口,负责发起配置、内存和I/O事务,通常集成在芯片组或SoC中。
- Switch:由一个上游端口和多个下游端口组成,内部包含虚拟PCI桥,实现事务的路由与转发。
- Endpoint:实际的PCIe设备,如网卡、NVMe SSD、GPU等,分为Type 0和Type 1两种配置空间类型。
- PCIe Link:连接两个端口之间的物理链路,由多条Lane组成,常见x1、x4、x8、x16。
在Linux内核中,PCIe拓扑通过pci_bus、pci_dev等结构体描述,每个设备都有唯一的BDF(Bus:Device.Function)编号。驱动开发者需要理解设备在拓扑中的位置,才能正确进行资源分配和中断路由。
二、PCIe分层协议:事务层、数据链路层与物理层
PCIe协议采用三层结构,自上而下分别为事务层(Transaction Layer)、数据链路层(Data Link Layer)和物理层(Physical Layer)。每一层都有明确的职责,发送端逐层封装,接收端逐层解封装。
| 层次 | 核心职责 | 关键数据单元 | 驱动关注点 |
|---|---|---|---|
| 事务层 | 生成TLP,处理内存/IO/配置/消息事务 | TLP(Transaction Layer Packet) | BAR映射、DMA地址、中断 |
| 数据链路层 | 保证TLP可靠传输,差错检测与重传 | DLLP(Data Link Layer Packet) | 通常透明,驱动无需直接干预 |
| 物理层 | 差分信号发送/接收,链路训练与电源管理 | Ordered Set、Symbol | 链路速率、Lane宽度协商 |
事务层是驱动开发者最需要关注的层次。当驱动读写设备寄存器时,CPU发出的内存访问会被RC转换为Memory Read/Write TLP;当设备发起DMA时,设备生成Memory Read/Write TLP发往系统内存。理解TLP的格式和路由方式,有助于调试DMA异常和地址映射问题。
三、PCIe事务类型:四种基本事务
PCIe定义了四种基本事务类型,分别对应不同的地址空间和用途:
- Memory Transaction:读写系统内存或设备内存空间,是DMA和MMIO的基础。
- I/O Transaction:读写I/O地址空间,主要用于传统设备,现代PCIe设备较少使用。
- Configuration Transaction:读写配置空间,用于设备枚举、BAR分配和驱动探测。
- Message Transaction:传递中断、电源管理、错误报告等消息,替代传统PCI的中断引脚。
pci_read_config_dword()触发Configuration Read TLP,ioread32()触发Memory Read TLP,而MSI/MSI-X中断则通过Message TLP传递。
四、配置空间:设备的“身份证”与“资源清单”
每个PCIe功能都拥有256字节(PCIe扩展至4KB)的配置空间,前64字节是标准头部,包含Vendor ID、Device ID、Class Code、BAR、中断引脚等关键字段。配置空间是驱动识别设备、分配资源和使能设备的基础。
# 查看PCIe设备配置空间
lspci -vvv -s 01:00.0
# 输出示例:
01:00.0 Non-Volatile memory controller: Samsung Electronics Co Ltd NVMe SSD Controller
Control: I/O- Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx+
Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR-
Latency: 0
Interrupt: pin A routed to IRQ 16
Region 0: Memory at f7c00000 (64-bit, non-prefetchable) [size=16K]
Capabilities: [c0] Power Management version 3
Capabilities: [d0] MSI: Enable+ Count=1/1 Maskable- 64bit+
Capabilities: [e0] Express Endpoint, MSI 00
配置空间的前64字节中,Vendor ID和Device ID用于匹配驱动,Class Code用于分类设备,BAR用于声明设备需要的地址空间大小和类型。内核在枚举阶段会读取这些字段,并为设备分配资源。
五、BAR机制:设备地址空间的窗口
BAR(Base Address Register)是配置空间中的一组寄存器,用于声明设备需要的内存或I/O地址空间。每个BAR可以映射一段设备内部寄存器或内存,驱动通过BAR访问设备寄存器。
BAR的工作流程如下:
- 系统枚举时,向BAR写入全1,读取返回的掩码,确定BAR所需空间大小和对齐要求。
- 内核根据掩码为BAR分配一段系统物理地址,并写回BAR寄存器。
- 驱动通过
pci_iomap()或ioremap()将BAR物理地址映射到内核虚拟地址。 - 驱动使用
readl()/writel()等函数访问设备寄存器。
// 驱动中获取BAR并映射
struct pci_dev *pdev;
void __iomem *bar0;
resource_size_t bar0_start, bar0_len;
bar0_start = pci_resource_start(pdev, 0);
bar0_len = pci_resource_len(pdev, 0);
bar0 = pci_iomap(pdev, 0, bar0_len);
if (!bar0) {
dev_err(&pdev->dev, "Failed to map BAR0\n");
return -ENOMEM;
}
// 读写设备寄存器
u32 val = readl(bar0 + REG_OFFSET);
writel(val | BIT(0), bar0 + REG_OFFSET);
memcpy,必须使用readl/writel等I/O访问函数,否则可能因乱序或缓存问题导致设备异常。
六、案例与复盘:NVMe SSD的PCIe初始化
以NVMe SSD为例,其PCIe初始化过程如下:内核枚举时读取配置空间,识别为Class Code 0x010802的NVMe设备;读取BAR0,发现需要16KB非预取内存空间;内核分配物理地址并写回BAR0;nvme驱动通过pci_iomap映射BAR0,访问NVMe控制器寄存器;使能Bus Master和MSI-X中断;最后通过DMA读写NVMe队列。
常见问题包括:BAR空间未正确映射导致寄存器读写失败;MSI-X中断未使能导致I/O超时;DMA地址未正确设置导致数据错误。排查时可通过lspci -vvv查看BAR分配和中断状态,通过dmesg查看内核枚举日志,通过/proc/iomem确认BAR物理地址范围。