1. PCIe基础回顾:从体系结构到配置空间

各位同学,大家好。我是你们的PCIe系统架构讲师。今天咱们正式开始《PCIe Switch非透明桥与隔离技术实战》的第一章——基础回顾。

说实话,每次开课我都要纠结一下:基础讲多深?讲太浅,有经验的同学觉得浪费时间;讲太深,新手又跟不上。我的习惯是——把核心概念讲透,把容易踩坑的地方点出来。至于那些手册上随便就能查到的寄存器位定义,咱们就不啰嗦了。

1.1 PCIe体系结构概述

PCIe,全称Peripheral Component Interconnect Express。说白了,就是电脑里连接CPU和各种外设的高速总线。

你想想看,从最早的PCI并行总线,到PCI-X,再到今天的PCIe,为什么PCIe能成为绝对主流?核心原因就两个:串行传输和点对点连接

串行传输意味着不需要像PCI那样同步所有设备的时钟,频率可以跑得更高。点对点连接意味着每个设备都有自己的专用通道,不用像PCI那样共享总线带宽。

我记得刚入行那会儿,有个老工程师跟我说过一句话,我一直记到现在:「PCIe的本质,就是一组高速串行差分对,加上一套复杂的协议栈」。嗯,这句话虽然简单,但确实点到了精髓。

核心要点:

  • PCIe是串行总线,使用差分信号对(Tx+/Tx-, Rx+/Rx-)
  • 点对点连接,每个链路(Link)由多条通道(Lane)组成
  • 支持1、2、4、8、16、32通道宽度(x1, x2, x4, x8, x16, x32)
  • 每个通道速率从Gen1的2.5GT/s到Gen6的64GT/s

1.2 分层模型:事务层、数据链路层、物理层

PCIe协议栈分为三层,从高到低分别是:事务层(Transaction Layer)、数据链路层(Data Link Layer)、物理层(Physical Layer)

为什么要分层?说白了就是为了解耦。每一层只管自己的事,上层不用关心底层怎么传,底层也不用关心上层传的是什么数据。

我画了一张图,帮大家理解这三层的关系:

PCIe协议栈分层模型 设备A(RC/EP/Switch) 设备B(RC/EP/Switch) 事务层 TLP生成/解析 事务排序/流量控制 事务层 TLP生成/解析 事务排序/流量控制 数据链路层 DLLP交换 ACK/NAK重传 数据链路层 DLLP交换 ACK/NAK重传 物理层 8b/10b或128b/130b编码 串行/解串 物理层 8b/10b或128b/130b编码 串行/解串 TLP DLLP 差分信号对 事务层 数据链路层 物理层 数据流向:应用 → 事务层 → 数据链路层 → 物理层 → 链路 → 物理层 → 数据链路层 → 事务层 → 应用

每一层都有自己特定的职责:

  • 事务层:负责生成和解析TLP(事务层包)。比如CPU要读一个Endpoint的寄存器,事务层就会构造一个Memory Read TLP。我习惯把事务层理解为「翻译官」——把上层要做的读写操作翻译成PCIe协议能理解的格式。
  • 数据链路层:负责可靠传输。它会给TLP加上序列号和CRC校验,接收方收到后要回复ACK或NAK。说白了,这一层就是「快递员」,保证包裹不丢、不坏。
  • 物理层:负责真正的电气信号传输。包括串行化/解串行化、编码(Gen1/2用8b/10b,Gen3+用128b/130b)、时钟恢复等。这一层是「搬运工」,把0和1变成电压信号在线路上跑。

我的经验之谈:

调试PCIe问题的时候,一定要先搞清楚问题出在哪一层。链路训练不上?查物理层。TLP超时?查事务层。CRC错误?查数据链路层。我曾经花了两天时间在事务层找问题,最后发现是物理层的信号完整性不行——这种教训,一次就够了。

1.3 PCIe拓扑结构:Root Complex、Switch、Endpoint

PCIe的拓扑结构,说白了就是一个树形结构。根节点是Root Complex(RC),叶子节点是Endpoint(EP),中间可以有Switch做扩展。

咱们来看一下典型的拓扑:

PCIe典型拓扑结构 CPU Root Complex (RC) 内部集成PCIe控制器 Endpoint 例如:NVMe SSD PCIe Switch 上行端口→RC,下行端口→EP Endpoint 例如:GPU Endpoint 例如:网卡 Root Complex Switch Endpoint

三种设备类型,各有各的定位:

  • Root Complex (RC):系统的根节点,通常集成在CPU内部。RC负责连接CPU、内存和PCIe子系统。说白了,RC就是PCIe世界的「交通指挥中心」。
  • Switch:相当于PCIe的「交换机」。它有一个上行端口(Upstream Port)连RC,多个下行端口(Downstream Port)连Endpoint。Switch的作用就是扩展端口数量,让一个RC能带更多的设备。
  • Endpoint (EP):功能设备,比如显卡、NVMe SSD、网卡等。EP是PCIe事务的发起者或目标者。

这里有个容易混淆的点:

很多人以为Switch就是「一分多」的Hub。其实不然。PCIe Switch内部是一个路由矩阵,它根据TLP中的地址或ID信息,把事务转发到正确的端口。而且Switch本身不处理事务内容,它只负责转发。

1.4 PCIe配置空间与BAR空间基础

这部分是咱们课程的重中之重。非透明桥的核心操作,说白了就是操作配置空间和BAR空间。

每个PCIe设备(包括RC、Switch、Endpoint)都有一个配置空间,大小是4KB(PCI时代只有256字节,PCIe扩展到了4KB)。配置空间里存放着设备的类型、厂商ID、设备ID、状态寄存器、控制寄存器等信息。

系统启动时,BIOS或操作系统会枚举所有PCIe设备,读取它们的配置空间,分配资源(比如BAR地址、中断号等)。这个过程叫PCIe枚举

咱们来看一下配置空间的前64字节(Header Type 0,适用于Endpoint):

偏移地址 寄存器名称 说明
0x00 Vendor ID / Device ID 厂商ID(如8086h=Intel)和设备ID
0x04 Command / Status 命令寄存器(控制IO/Mem访问等)和状态寄存器
0x08 Revision ID / Class Code 版本号和类别码(如010000h=大容量存储控制器)
0x0C Cache Line Size / Latency Timer / Header Type Header Type指示设备类型(0=EP,1=Switch上行端口,2=桥)
0x10-0x24 Base Address Registers (BAR0-BAR5) 6个BAR寄存器,每个32位或64位,用于地址映射
0x3C Interrupt Line / Interrupt Pin 中断配置

BAR空间是啥?说白了,就是设备暴露给系统的一块「窗口」。系统给设备分配一段物理地址空间,设备把内部的寄存器或内存映射到这段地址上。CPU读写这段地址,就相当于读写设备内部的寄存器。

举个例子:

// 假设系统给设备分配了BAR0,基地址为0xF0000000,大小为1MB
// 设备内部寄存器偏移如下:
// 0x0000 - 控制寄存器
// 0x0004 - 状态寄存器
// 0x1000 - 数据缓冲区

// CPU要写控制寄存器:
*(volatile uint32_t *)(0xF0000000) = 0x01;  // 写BAR0基地址+0x0000

// CPU要读状态寄存器:
uint32_t status = *(volatile uint32_t *)(0xF0000004);  // 读BAR0基地址+0x0004

这段代码看起来简单,但实际项目中坑不少。我遇到过最典型的问题:BAR地址对齐。BAR的大小必须是2的幂,而且基地址必须按大小对齐。比如1MB的BAR,基地址必须是1MB的整数倍。如果你在FPGA里实现PCIe设备,BAR大小设错了或者对齐不对,系统枚举时就会分配失败。

避坑指南:

我曾经在一个项目里,把BAR的大小设成了0x100000(1MB),但实际需要的空间只有0x80000(512KB)。结果系统给分配了1MB空间,但设备只解码了512KB。CPU访问高512KB时,设备没响应,导致系统挂死。嗯,从那以后我每次都会仔细核对BAR的大小和实际解码范围。

对于Switch来说,配置空间稍微复杂一些。Switch的每个端口都有自己的配置空间,而且上行端口和下行端口的Header Type不同。上行端口是Type 1(桥),下行端口也是Type 1。Type 1的配置空间里包含Primary Bus Number、Secondary Bus Number、Subordinate Bus Number,这些是Switch做总线号路由的关键。

咱们后面讲非透明桥的时候,会频繁操作这些配置空间寄存器。所以现在打好基础,后面才能跟得上。

小结

这一章咱们回顾了PCIe的基础知识:分层模型、拓扑结构、配置空间和BAR空间。这些东西看起来简单,但都是后面非透明桥技术的基石。

我个人觉得,学PCIe最好的方法就是「动手」。找个带PCIe的开发板,用lspci命令看看设备的配置空间,用devmem2工具读写BAR空间,比看十遍手册都管用。

好,第一章就到这里。下一章咱们开始进入正题——非透明桥的原理和应用场景。


公众号:蓝海资料掘金营,微信deep3321