1. PCIe简介与基础概念
做DMA传输,绕不开PCIe。不管是GPU加速、NVMe硬盘,还是万兆网卡,底层全是PCIe在撑着。这一章,我们把PCIe的底子捋一遍。总线架构、拓扑结构、链路分层,这三个概念搞清楚了,后面调DMA性能才能有的放矢。
从并行到串行——PCIe的诞生
早期PC用PCI总线,32位宽,133MB/s封顶。后来PCI-X到了1066MB/s,但并行总线的瓶颈摆在那——信号同步越来越难。频率一高,时钟偏移、信号串扰全来了。设计起来费劲,成本也压不住。
我记得2008年做一块工业相机驱动,用的还是PCI接口。那时候PCIe 1.0已经普及了,单通道2.5GT/s,x16就是4GB/s。我当时的第一反应是:早该换串行了。说白了,PCIe就是把并行的地址/数据/控制信号,全部转成串行的差分对来传输。频率可以拉得更高,而且每条通道独立,不再像PCI那样共享总线。
为什么要搞这么复杂?你想想看,并行总线要同时对齐几十根信号线,PCB走线都得等长,到了高频根本扛不住。PCIe用差分对,一对线传数据,一对线传时钟,抗干扰能力强得多。
| 特性 | PCI / PCI-X | PCIe |
|---|---|---|
| 总线类型 | 并行共享总线 | 串行点对点 |
| 信号线数 | 64根以上 | 每通道4根(2收2发) |
| 带宽分配 | 所有设备共享 | 每个设备独享 |
| 频率提升 | 受限于同步 | 可大幅提升 |
总线架构——三个核心角色
PCIe的总线架构不复杂,核心角色就三个:Root Complex(RC)、Switch、Endpoint。偶尔还会看到Bridge,但原则上是这三类。
Root Complex 是系统的"大总管"。它连接CPU、内存,把处理器的内存访问请求转换成PCIe事务,发往下游设备。反过来,设备发出的DMA请求也是通过RC到达内存的。说白了,RC就是Host侧和PCIe域之间的翻译官。
Switch 负责扩展。一个RC能挂的设备数量有限,通过Switch可以接出更多Endpoint。Switch内部有路由逻辑,根据TLP包头的地址或ID,把包转到对应的下游端口。
Endpoint 就是实际的设备——GPU、NVMe SSD、网卡、FPGA加速卡。Endpoint主动发起DMA,或者响应RC的配置读写。
我刚开始做PCIe驱动时,把Endpoint和RC的角色搞反过一次。当时调一个FPGA卡,设备收不到DMA完成通知,查了一整天,最后发现是TLP包的路由类型配错了。从那以后,我每次都会先确认设备在拓扑中的位置。
拓扑结构——树形互联
PCIe的拓扑是标准的树形结构。RC是根,下面挂Switch,Switch下面再挂Endpoint。每个设备通过独立的Link连接到上游,带宽独享,不打架。
这种拓扑带来的好处很明显:
- 点对点连接:两个设备通信不干扰其他设备
- 带宽独享:每个Link的带宽归连接的设备单独使用
- 热插拔支持:树形结构便于枚举和管理
我在项目中遇到过一个问题:一块GPU和一块NVMe SSD挂在同一个Switch下面,同时做大数据传输时总带宽上不去。后来发现是Switch的上行Link成了瓶颈。所以,做DMA性能调优时,一定要先搞清楚拓扑——设备挂在哪,中间经过几级Switch。
核心要点:PCIe不是共享总线,是树形拓扑的点对点网络。每个设备独享Link带宽,但经过Switch时可能共享上行链路。DMA性能调优的第一步,就是把拓扑画清楚。
链路分层——协议栈的骨架
PCIe的协议栈分三层:事务层、数据链路层、物理层。每层各管一摊事,上层只管封装,下层只管传输。
事务层是最高层。它负责生成和解析TLP(事务层包)。读写请求、完成包、消息,全在这一层搞定。驱动开发打交道最多的就是事务层——配置设备的BAR空间、发起DMA读写,本质都是在构造TLP。
数据链路层夹在中间,负责可靠传输。它给TLP加上序列号和CRC,收方校验,发现错误就触发重传。这一层对上层是透明的,但出了问题很头疼。
我曾经调一个FPGA卡,DMA写到一半总卡死。示波器抓Link信号也没看出物理层异常。后来开了PCIe的Debug寄存器,发现数据链路层一直在重传同一个TLP。原来是FPGA侧的数据链路层状态机有个边界条件没处理好。所以,链路层问题通常表现为"偶尔卡死"或"吞吐骤降",排查起来特别费劲。
物理层在最底下,管电气特性。串行/解串、8b/10b编码(Gen3之前)、时钟恢复,都是物理层的活。物理层把DLLP和TLP变成比特流,从差分线上发出去。
| 层级 | 核心职责 | 处理单元 | 驱动关注点 |
|---|---|---|---|
| 事务层 | TLP的生成与解析 | TLP(事务层包) | 配置空间、DMA描述符、中断 |
| 数据链路层 | 可靠传输、重传、ACK/NAK | DLLP(数据链路层包) | 重传计数、链路状态寄存器 |
| 物理层 | 电气特性、编解码、时钟恢复 | 有序集(Ordered Set) | Link Width、Link Speed、信号完整性 |
我个人习惯把这三层想象成一个"发货流程":事务层是销售部,写好发货单(TLP);数据链路层是物流部,给发货单贴上条码和防伪码(序列号+CRC);物理层是运输队,把包裹装车发货。任何一个环节出问题,货都到不了客户手里。
一个小技巧:调DMA时如果吞吐不稳,先查物理层的Link Width和Speed有没有降速。很多时候是信号质量问题导致链路降级,不是软件写错了。
避坑指南:我曾经遇到过设备枚举正常,但DMA就是不走的情况。查了三天,发现是Root Complex到Switch之间的链路因为PCB走线过长,Gen3降到了Gen2。驱动没做链路协商状态检查,导致带宽预估完全不准。所以,驱动初始化时一定要读一下Link Status寄存器,确认实际速率和宽度。