admin 管理员组

文章数量: 887021

本文总结 lspci 相关的知识点 1‘ 2’ 3‘ 4’ 5’ 6。
本文的内容主要源自互联网技术博客及 SSDfans 网站 7。

持续更新中 …

Update: 2023 / 3 / 23


PCIe | 基础知识点扫盲

  • 总览
    • 总线,PCI 和 PCIe
      • 总线
      • PCI
      • PCIe
    • 发展
    • 连接
      • PCIe
      • 特点
      • 带宽
      • SATA
    • 寻址
      • 示例
        • 网卡设备
          • 绑定与解绑
            • 异常处理
          • I / O
        • 存储设备
          • spdk
            • 绑定与解绑
            • I / O
          • nvme
            • 绑定与解绑
            • I / O
    • 拓扑结构
      • PCI
      • PCIe
        • Root Complex
        • PCIe Endpoint
        • Switch
      • 总结
      • 问题
    • 分层结构
      • 数据传输
        • PCIe Endpoint 设备
          • 数据打包发送
          • 数据剥离接收
        • Switch
        • Root Complex & Switch
    • PCIe TLP
      • TLP 类型
        • Non-posted / Posted
          • 示例
            • Memory Read
            • Memory Write
      • TLP 结构
        • Header
          • Memory TLP
        • Configuration TLP
        • Message TLP
        • Completion TLP
      • TLP 路由
        • 地址路由
          • Endpoint
          • Switch
            • Upstream -> Downstream
            • Downstream -> Upstream
        • ID 路由
          • Endpoint
          • Switch
        • 隐式路由
          • Endpoint
          • Switch
    • PCIe 配置和地址空间
      • 概念
        • PCI
        • PCIe
      • 配置空间结构
        • PCIe Header (0 ~ 64 bytes)
        • PCIe Capability (64 ~ 256bytes)
        • PCIe 扩展配置空间 (256 ~ 4K bytes)
      • 配置空间访问
    • 数据链路层
      • ACK / NAK 协议
      • TLP 流控(流量控制,Flow Control)
      • 电源管理
    • 物理层
      • 逻辑子层
    • PCIe Reset
      • 种类
      • 功能、实现方式及对设备的影响
        • Fundamental Reset
          • 功能及对设备的影响
          • 实现方式
            • PERST#
            • 复位信号
        • Non-Fundamental Reset
          • 实现
        • Functional Level Reset
          • 功能及对设备的影响
          • 实现方式
    • PCIe Max Payload Size 和 Max Read Request Size
      • PCIe Max Payload Size
        • 概念
        • 作用
        • 查询
        • 设置
        • 内核实现
          • MPS 配置类型
          • 内核配置 MPS
      • PCIe Max Read Request Size
        • 概念
        • 作用
        • 查询
    • PCIe 链路性能损耗
  • PCIe 链路训练、枚举扫描、配置BAR
  • PCIe 枚举扫描
    • 枚举扫描过程
    • Linux 与 PCIe 设备树
  • PCIe 供电
  • 工具
    • lspci
    • setpci
      • 示例
        • 1.
  • 参考链接


总览

总线,PCI 和 PCIe

总线

什么是总线?总线是一种传输信号的路径或信道。
典型情况是,总线是连接于一个或多个导体的电气连线,总线上连接的所有设备可在同一时间收到所有的传输内容。

总线由电气接口和编程接口组成。


PCI

PCIPeripheral Component Interconnect(外围设备互联)的简称,是普遍使用在桌面及更大型的计算机上的外设总线规范
操作系统中的 PCI/PCI-E 设备驱动以及操作系统内核,都需要访问 PCIPCI-E 配置空间。PCI / PCI-E 设备的正常运行,离不开 PCI / PCI-E 配置空间。通过读写 PCI / PCI-E 配置空间,可以更改设备运行参数,优化设备运行。

Linux 内核中,为 PCI / PCI-E 只适用了一种总线 PCI(内核提供的总线系统),故访问 PCI-E 配置空间,也包括了 PCI 设备配置空间。


PCIe

PCI-Express ( peripheral component interconnect express ),简称 PCIe,是一种高速串行计算机扩展总线标准,主要用于扩充计算机系统总线数据吞吐量以及提高设备通信速度。

PCIe 本质上是一种全双工的的连接总线,传输数据量的大小由通道数 lane 决定的。一般,1 个连接通道 lane 称为 X1,每个通道 lane2 对数据线组成,1 对发送,1 对接收,每对数据线包含两根差分线。即 X1 只有 1lane4 根数据线,每个时钟每个方向 1bit 数据传输。依次类推,X2 就有 2lane,由 8 根数据线组成,每个时钟传输 2bit。类似的还有X12X16X32

PCI 并行总线不同,PCIe 的总线采用了高速差分总线,并采用端到端的连接方式, 因此在每一条 PCIe 链路中两端只能各连接一个设备, 如果需要挂载更多的 PCIe 设备,那就需要用到Switch 转接器,如下所示 8:


发展

接口的发展的关键时间节点如下:

时间接口
1987ISA
1992PCI
2003PCIe Gen1
2017PCIe Gen5
todoPCIe Gen6

SSD 采用 PCI 接口是因为它比 SATA 快。

PCIe 是从 PCI 发展过来的,PCIeexpress 的简称,表示
因为 PCIe 使用的是串口传输,而 PCI 使用并口传输(如下图所示)。

  • 使用并口传输,虽然在并口时可以同时传输若干比特(比串口 ”划算“),但是接受端必须等最慢的那个 bit 数据到了以后才能锁住整个数据。
    随着技术发展,要求数据传输速率越来越快,意味着对时钟频率要求也越来越高。
    在发送端发送出数据后,若需在接收端正确采集到数据,要求时钟的周期必须大于数据传输的时间(从发送端到接收端的时间,Flight Time), 受限于数据传输时间,时钟频率也不能太高(周期不能太短)。此外,时钟信号信号在线上传输时也会出现相位偏移( Clock Skew),影响接收端的数据采集。
  • 使用串口传输,不需要外部时钟信号(因此不会有相位偏移的问题),时钟信号通过 8 / 10 编码或者 128 / 130 编码嵌入在数据流中,接收端可从数据流中恢复时钟信息。

连接

PCIe

  • 2个 PCIe 设备之间通过多个 Plane,比如1,2,4,8,12,16,32个 lane 相互连接。就像高速公路一样,有单车道、2车道、4车道等。
    常见的是4个 lane 及以下,但 PCIe 是可以最多有 32lane 的。
  • 每个 Lane 由1个 Tx 和 1个 Rx 组成,每个 Tx / Rx 由一对差分信号( differential signal )线组成,包含4条物理线。差分信号,抗干扰能力强,需要的电压值低。

差分信号( Differential Signal )在高速电路设计中的应用越来越广泛,电路中最关键的信号往往都要采用差分结构设计,什么另它这么倍受青睐呢?

a. 抗干扰能力强,因为两根差分走线之间的耦合很好,当外界存在噪声干扰时,几乎是同时被耦合到两条线上,而接收端关心的只是两信号的差值,所以外界的共模噪声可以被完全抵消。

b. 能有效抑制 EMI,同样的道理,由于两根信号的极性相反,他们对外辐射的电磁场可以相互抵消,耦合的越紧密,泄放到外界的电磁能量越少。

c. 时序定位精确,由于差分信号的开关变化是位于两个信号的交点,而不像普通单端信号依靠高低两个阈值电压判断,因而受工艺,温度的影响小,能降低时序上的误差,同时也更适合于低幅度信号的电路。目前流行的 LVDSlow voltage differential signaling )就是指这种小振幅差分信号技术。

  • 2个设备之间的 PCIe 连接,叫做一个 Link。两个 PCIe 设备之间,有专门的发送和接收通道,数据可以同时往2个方向传输,PCIe Spec 称这种工作模式为双单工模式( Dual-Simplex),可以理解为 全双工模式

打个比方,常见的 Gen4 速率的 PCIe 设备支持 Gen4 x 4,即双向的8通道高速公路(每个方向4通道),每对双向通道的 带宽 可达 4GB/s 的读写带宽(读带宽 2GB/s 或 写带宽 2GB/s)。


特点

  1. 对称性,每个连接在每个方向要支持对称数量的 Lane
  2. PCIe 是点对点的串行连接;
  3. 全双工 (支持同时收发)

带宽

PCIe 是串行总线。
PCIe3.0 为例,线上的比特传输速率为 8 Gbps,物理层使用 128/130 编码进行数据传输,即 128 bit 的数据,实际在物理线路上是需要传输 130 bit 的,多余的 2 bit 用来校验。因此:
PCIe3.0 x 1 的带宽 = (8 Gbps x 2 (双向通道) x ( 128 bit / 130 bit ) / 8 ~= 2 GB/s
有多少条 Lane,带宽就是 2 GB / s 乘以 Lane 的数目。

由于采用了 128 / 130 编码,即每128 bit的数据,只额外增加了 2 bit 的开销,有效数据传输比率增大。虽然线上比特传输率没有翻倍,但有效数据带宽还是在 PCIe 2.0 的基础上实现了翻倍。


SATA


PCIe 一样,SATA 也有独立的发送和接收通道。
但和 PCIe 不同的是,同一时间只有一条通道可以进行数据传输。换句话说,在一条通道上发送数据时在另一条通道上就不能接收数据。反之亦然。这种工作模式称为半双工模式。

打个比方,PCIe 就像双向车道,SATA 是单向车道。


寻址

先来看一个例子,我的电脑装有 1GRAM1G 以后的物理内存地址空间都是外部设备 IO 在系统内存地址空间上的映射。

/proc/iomem 描述了系统中所有的设备 I/O 在内存地址空间上的映射。我们来看地址从 1G 开始的第一个设备在 /proc/iomem 中是如何描述 的:

             40000000-400003ff : 0000:00:1f.1

这是一个 PCI 设备,40000000-400003ff 是它所映射的内存地址空间,占据了内存地址空间的 1024 bytes 的位置。
0000:00:1f.1 则是一个 PCI 外设的地址, 它以冒号和逗号分隔为 4 个部分,第一个 16 位表示域,第二个 8 位表示一个总线编号,第三个 5 位表示一 个设备号,最后是 3 位,表示功能号
因为 PCI 规范允许单个系统拥有高达 256 个总线,所以总线编号是 8 位。但对于大型系统而言,这是不够的,所以,引入了域的概念,每个 PCI 域可以拥有最多 256 个总线,每个总线上可支持 32 个设备,所以设备号是 5 位,而每个设备上最多可有 8 种功能,所以功能号是 3 位。由此,我们可以得 出上述的 PCI 设备的地址是 0 号域 0 号总线上的 31 号设备上的 1 号功能。

那上述的这个 PCI 设备到底是什么呢?下面从主机上使用 lspci 命令所获得的输出内容:

00:00.0 Host bridge: Intel Corporation 82845 845 (Brookdale) Chipset Host Bridge (rev 04)
00:01.0 PCI bridge: Intel Corporation 82845 845 (Brookdale) Chipset AGP Bridge(rev 04)
00:1d.0 USB Controller: Intel Corporation 82801CA/CAM USB (Hub #1) (rev 02)
00:1d.1 USB Controller: Intel Corporation 82801CA/CAM USB (Hub #2) (rev 02)
00:1e.0 PCI bridge: Intel Corporation 82801 Mobile PCI Bridge (rev 42)
00:1f.0 ISA bridge: Intel Corporation 82801CAM ISA Bridge (LPC) (rev 02)
00:1f.1 IDE interface: Intel Corporation 82801CAM IDE U100 (rev 02)
00:1f.3 SMBus: Intel Corporation 82801CA/CAM SMBus Controller (rev 02)
00:1f.5 Multimedia audio controller:Intel Corporation 82801CA/CAM AC'97 Audio Controller (rev 02)
00:1f.6 Modem: Intel Corporation 82801CA/CAM AC'97 Modem Controller (rev 02)
01:00.0 VGA compatible controller: nVidia Corporation NV17 [GeForce4 420 Go](rev a3)
02:00.0 FireWire (IEEE 1394): VIA Technologies, Inc. IEEE 1394 Host Controller(rev 46)
02:01.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8139/8139C/8139C+(rev 10)
02:04.0 CardBus bridge: O2 Micro, Inc. OZ6933 Cardbus Controller (rev 01)
02:04.1 CardBus bridge: O2 Micro, Inc. OZ6933 Cardbus Controller (rev 01)

lspci 没有标明域,但对于一台 PC 而言,一般只有一个域,即 0 号域。通过这个输出我们可以看到它是一个 IDE interface。由上述的 输出可以看到,我的电脑上共有 3PCI 总线( 0 号,1 号,2 号)。在单个系统上,插入多个总线是通过桥( bridge ) 来完成的,桥是一种用来连接总线的特殊 PCI 外设。所以,PCI 系统的整体布局组织为树型,我们可以通过上面的 lspci 输出,来画出我的电脑上的PCI 系统的树型结构:

00:00.0(主桥)--00:01.0(PCI桥)-----01:00:0(nVidia显卡)
                   |
                   |---00:1d(USB控制器)--00:1d:0(USB1号控制器)
                   |                    |
                   |                    |--00:1d:1(USB2号控制器)                    |
                   |-00:1e:0(PCI桥)--02:00.0(IEEE1394)
                   |                |
                   |                |-02:01.0(8139网卡)
                   |                |
                   |                |-02:04(CardBus桥)-02:04.0(1|                                   |
                   |                                   |--02:04.1(2)
                   |
                   |-00:1f(多功能板卡)-00:1f:0(ISA桥)
                                        |
                                        |--00:1f:1(IDE接口)
                                        |
                                        |--00:1f:3(SMBus)
                                        |
                                        |--00:1f:5(多媒体声音控制器)
                                        |
                                        |--00:1f:6(调制解调器)

由上图可以得出,我的电脑上共有 8PCI 设备,其中 0 号总线上(主桥)上连有 4 个,1 号总线上连有 1 个,2 号总线上连有 3 个。00:1f 是一个连有 5 个功能的多功能板卡。

每一个 PCI 设备都有它映射的内存地址空间和它的 I/O 区域,这点是比较容易理解的。除此之外,PCI 设备还有它的配置寄存器。有了配置寄存器, PCI 的驱动程序就不需要探测就能访问设备。配置寄存器的布局是标准化的,配置空间的 4 个字节含有一个独一无二的功能 ID ,因此,驱动程序可通过查询外设的特定 ID 来识别其设备。所以,PCI 接口标准在 ISA 之上的主要创新在于配置地址空间。

前文已讲过,PCI 驱动程序不需要探测就能访问设备,而这得益于配置地址空间。在系统引导阶段,PCI 硬件设备保持未激活状态,但每个 PCI 主板均配备有能够处理 PCI 的固件,固件通过读写 PCI 控制器中的寄存器,提供了对设备配置地址空间的访问。

配置地址空间的前 64 字节是标准化的,它提供了厂商号,设备号,版本号等信息,唯一标识一个 PCI 设备。同时,它也提供了最多可多达 6 个的 I/O 地址区域,每个区域可以是内存也可以是 I/O 地址。这几个 I/O 地址区域是驱动程序找到设备映射到内存和 I/O 空间的具体位置的唯一途径。

有了这两点, PCI 驱动程序就完成了相当于探测的功能。关于这 64 个字节的配置空间的详细情况,可参阅 《Linux设备驱动程序第三版》 P306,不再详述。


示例

网卡设备

参考这里 3’ 9

在自己的电脑上先用 lspci 命令检测使用的网络设备:

$ lspci | grep 'Eth'
07:00.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection (rev 03)

$ lspci -tv
- [0000:00]-+-00.0 Advanced Micro Devices, Inc. [AMD] ... Root Complex
           -+-01.0 Advanced Micro Devcies, Inc. [AMD] ... PCIe Dummy Host Bridge
           -+-01.2-[01-0c]----00.0-[-2-0c]--+02.0-[03-08]----00.0-[04-08]--+--01.0-[05]----00.0 Intel Corporation Wi-Fi 6 
           																   +--03.0-[06]--
           																   +--05.0-[07]----00.0 Intel Corportation 82574L Gigabit Network Connection
           																   ...
...     																           																   

这里使用的网络接口是虚拟网卡 82574L

注意这里的 07:00.0 表示的是网络接口对应的 pci 号,这些 pci 号唯一表示一个接口,在绑定驱动与解绑驱动时会使用到。

igb 驱动为例,查看 /sys/bus/pci/dirvers/igb 目录下的内容,

$ cd /sys/bus/pci/drivers/igb
0000:07:00.0 bind  module  new_id  remove_id  uevent  unbind

这里的 0000:07:00.0 表示绑定到 igb 驱动上的 pci 接口的 pci 号。


绑定与解绑

网卡接口绑定主要与 bindnew_id 两个特殊的文件有关,unbind 是解绑驱动过程中会使用到的文件。
具体的绑定与解绑的过程就是向这几个文件中写入规定格式的数据完成的。linux kernel 源码目录中的 ABI/testing/sysfs-bus-pci 4 对这几个文件的描述信息如下:


  • bind
What:		/sys/bus/pci/drivers/.../bind
What:		/sys/devices/pciX/.../bind
Date:		December 2003
Contact:	linux-pci@vger.kernel
Description:
		Writing a device location to this file will cause
		the driver to attempt to bind to the device found at
		this location.	This is useful for overriding default
		bindings.  The format for the location is: DDDD:BB:DD.F.
		That is Domain:Bus:Device.Function and is the same as
		found in /sys/bus/pci/devices/.  For example::

		  # echo 0000:00:19.0 > /sys/bus/pci/drivers/foo/bind

		(Note: kernels before 2.6.28 may require echo -n).

这里写入的 0000:00:19.0 就是上面我们提到过的 pci 号。
bind 文件写入每一个接口的 pci 号意味着我们可以将一个网卡上的不同口绑定到不同的驱动上 9。


  • unbind
What:		/sys/bus/pci/drivers/.../unbind
What:		/sys/devices/pciX/.../unbind
Date:		December 2003
Contact:	linux-pci@vger.kernel
Description:
		Writing a device location to this file will cause the
		driver to attempt to unbind from the device found at
		this location.	This may be useful when overriding default
		bindings.  The format for the location is: DDDD:BB:DD.F.
		That is Domain:Bus:Device.Function and is the same as
		found in /sys/bus/pci/devices/. For example::

		  # echo 0000:00:19.0 > /sys/bus/pci/drivers/foo/unbind

		(Note: kernels before 2.6.28 may require echo -n).

这里向 unbind 文件写入接口的 pci 号就会解除当前绑定的驱动。
一个接口可以不绑定到任何驱动上面,不过我们常常不会这样去做。


  • new_id
What:		/sys/bus/pci/drivers/.../new_id
What:		/sys/devices/pciX/.../new_id
Date:		December 2003
Contact:	linux-pci@vger.kernel
Description:
		Writing a device ID to this file will attempt to
		dynamically add a new device ID to a PCI device driver.
		This may allow the driver to support more hardware than
		was included in the driver's static device ID support
		table at compile time.  The format for the device ID is:
		VVVV DDDD SVVV SDDD CCCC MMMM PPPP.  That is Vendor ID,
		Device ID, Subsystem Vendor ID, Subsystem Device ID,
		Class, Class Mask, and Private Driver Data.  The Vendor ID
		and Device ID fields are required, the rest are optional.
		Upon successfully adding an ID, the driver will probe
		for the device and attempt to bind to it.  For example::

		  # echo "8086 10f5" > /sys/bus/pci/drivers/foo/new_id

new_id 中写入设备 id,将会动态的在 pci 设备驱动中添加一个新的设备 id。这种功能允许驱动添加更多的硬件而非仅有在编译时包含到驱动中的静态支持设备 ID 列表中的硬件。

写入这个文件的格式中,Vendor IdDevice Id 字段是必须的,其它的字段可以不指定。

成功添加一个设备 ID 时,驱动会尝试 probe 系统中匹配到的设备并尝试绑定到它之上。


  • remove_id
What:		/sys/bus/pci/drivers/.../remove_id
What:		/sys/devices/pciX/.../remove_id
Date:		February 2009
Contact:	Chris Wright <chrisw@sous-sol>
Description:
		Writing a device ID to this file will remove an ID
		that was dynamically added via the new_id sysfs entry.
		The format for the device ID is:
		VVVV DDDD SVVV SDDD CCCC MMMM.	That is Vendor ID, Device
		ID, Subsystem Vendor ID, Subsystem Device ID, Class,
		and Class Mask.  The Vendor ID and Device ID fields are
		required, the rest are optional.  After successfully
		removing an ID, the driver will no longer support the
		device.  This is useful to ensure auto probing won't
		match the driver to the device.  For example::

		  # echo "8086 10f5" > /sys/bus/pci/drivers/foo/remove_id

remove_id 中写入的格式与 new_id 的写入格式相同。

写入 remove_id 可以用来确保内核不会自动 probe 匹配到这个驱动的设备。


先写入数据到 new_id 添加设备 id 然后进行绑定。
这样确保了首先有注册的设备 id,有了这个设备 id 总线才能够 match 到驱动执行 probe 操作。
没有注册的设备 idpci 总线不会匹配到指定的驱动,也无法将设备绑定到相应的驱动上。

为了成功绑定接口到 igb 上,我们首先需要在 igb 中添加支持的设备,这个可以通过写入数据到 new_id 添加设备 id 后写入 bind 文件来完成。

同一个设备 id 可以写入多次到 new_id 中,要移除也需要写入相同的次数到 remove_id 中。注意写入到 remove_id 并不会解除绑定。

new_id 的写入的参数中没有 pci 号,因此不能指定只绑定相同型号网卡的单个口到驱动中。除非其它口已经绑定到了其它驱动,不然这些口都会被绑定。


异常处理

如果绑定失败的情况,可能导致该结果的情形有:

  • new_id 没有添加,不会 match 到指定的驱动;
  • probe 过程异常,绑定失败;

通常,可以通过查看 dmesg 信息来分析定位问题所在。


当写入设备 idnew_id 文件中会出触发总线匹配系统中的接口,属于写入的设备 id 的设备并且没有绑定到任何驱动上的接口将会全部会被绑定到 new_id 所属的驱动。

例如系统中有两个 82574L 网卡接口,都没有绑定驱动,这时我们写入 82574L 的设备 idigb 驱动对应的 new_id 文件中,会导致这两个口都绑定到 igb 上。

如果这种行为对功能有所影响,可以选择在绑定到 igb 之前先将接口绑定到其它驱动上(一般是官方驱动),这样在写入 new_id 文件时,已经绑定到其它驱动的接口就会被 skip


I / O

可以在目录 /sys/bus/pci/drivers/ 下看到很多以驱动命名的文件夹。在相应的文件夹中,存在以 PCI 设备名命名的子文件夹,但不是说这些设备都存在于你的系统中。

下面来看一下该网卡设备的配置空间的详细情况。
我们进入 /sys/bus/pci/drivers/igb/ 目录,其中有一个以它的设备地址 0000:07:00.0 命名的目录。在这个目录下可以找到该网卡设备相关的很多信息。其中 resource 记录了它的 6I/O 地址区域。内容如下:

         0x00000000fc300000 0x00000000fc31ffff 0x0000000000040200
         0x0000000000000000 0x0000000000000000 0x0000000000000000
         0x000000000000f000 0x000000000000f01f 0x0000000000040101
         0x00000000fc320000 0x00000000fc323fff 0x0000000004020000
         0x0000000000000000 0x0000000000000000 0x0000000000000000
         0x0000000000000000 0x0000000000000000 0x0000000000000000
         0x0000000000000000 0x0000000000000000 0x0000000000000000

由该文件可以看出,此设备使用了两个 I/O 地址区域,一个映射了 I/O 端口范围,一个是它映射的内存地址空间。

关于这两个值可以在 /proc/iomem/proc/ioports 中得到验证,

$ cat /proc/iomem | grep '0000:07:00.0'
	fc300000-fc31ffff : 0000:07:00.0
	fc320000-fc323fff : 0000:07:00.0
	
$ cat /proc/ioports | grep '0000:07:00.0'
	f000-f01f : 0000:07:00.0

存储设备

参考这里 3’ 10’ 11

在自己的电脑上先用 lspci 命令检测使用的非易失性存储设备:

$ lspci | grep 'Non-Volatile'
01:00.0 Non-Volatile memory controller: ... Device (rev 01)			
02:00.0 Non-Volatile memory controller: ... Device (rev 01)					           																   

这里的 01:00.002:00.0 表示的是 SSD 设备对应的 pci 号。

spdk

spdk 驱动为例,驱动加载成功后可以在 /sys/module 路径下找到 uio_pci_generic,代表驱动加载成功。也可以通过 lsmod | grep uio 命令来确认驱动是否加载成功。

查看 /sys/bus/pci/dirvers/uio_generic 目录下的内容可确定加载了 spdk 驱动的设备信息,

$ cd /sys/bus/pci/drivers/uio_generic
0000:01:00.0 0000:02:00.0 bind  module  new_id  remove_id  uevent  unbind

这里的 0000:01:00.00000:02:00.0 表示绑定到 spdk 驱动上的 pci 接口的 pci 号。

也可以在 /dev 下看到多个 uio 设备。

# ls /dev/uio*
/dev/uio0 /dev/uio1

绑定与解绑

解绑一个设备,只需将设备的 pci 总线 bdf 号写入 /sys/bus/pci/drivers/uio_generic/不同的设备驱动不同/unbind 即可,比如,

echo 0000:01:00.0 > /sys/bus/pci/drivers/uio_generic/unbind

绑定一个设备,和解绑类似,将设备的 pci 总线 bdf 号写入 /sys/bus/pci/drivers/uio_generic/不同的设备驱动不同/bind,比如,

echo 0000:01:00.0 > /sys/bus/pci/drivers/uio_generic/bind

然后,查看如下目录 /sys/bus/pci/devices/0000:01:00.0/ 是否有 uio 子目录,例如下面这样表示驱动绑定成功。

$ ll /sys/bus/pci/devices/0000\:01\:00.0/uio
total 0
drwxr-xr-x 3 root root 0  626 14:52 ./
drwxr-xr-x 5 root root 0  618 23:54 ../
drwxr-xr-x 3 root root 0  626 14:52 uio0/

I / O

可以在目录 /sys/bus/pci/drivers/uio_generic 下看到很多以 PCI 设备名命名的目录,但不是说这些设备都存在于你的系统中。

以其中一个设备为例,进入 0000:01: 00.0 命名的目录。在这个目录下可以找到该设备相关的很多信息。其中 uio/uio0/device/resource 记录了它的 6I/O 地址区域。内容如下:

         0x00000000fcb00000 0x00000000fcb03fff 0x0000000000140204
         0x0000000000000000 0x0000000000000000 0x0000000000000000
         0x0000000000000000 0x0000000000000000 0x0000000000000000
         0x0000000000000000 0x0000000000000000 0x0000000000000000
         0x0000000000000000 0x0000000000000000 0x0000000000000000
         0x0000000000000000 0x0000000000000000 0x0000000000000000
         0x0000000000000000 0x0000000000000000 0x0000000000000000
$ cat /proc/iomem | grep '0000:01:00.0'
	fcb00000-fcb03fff : 0000:01:00.0
	
$ cat /proc/ioports | grep '0000:01:00.0'


nvme

参考这里 12

linux 内核从 2.6.13-rc3 开始,提供了在用户空间动态的绑定和解绑定设备和设备驱动之间关系的功能。
在这之前,只能通过 insmodmodprobe )和 rmmod 来绑定和解绑,而且这种绑定和解绑都是针对驱动和所有设备的。而新的功能可以设置驱动和单个设备之间的联系。

这里,我们以 pci 总线的 nvme ssd 为例,首先执行 lspci 显示所有的 nvme ssd

# lspci | grep memory
10:00.0 Non-Volatile memory controller: Samsung Electronics Co Ltd Device a802 (rev 01)

有如上这么多 nvme ssd,那么可以在 /dev 下看到多个 nvme 设备( OS 启动后默认加载 nvme 驱动)。

# ls /dev/nvme*
/dev/nvme0 /dev/nvme0n1

同时,对于所有的 nvme 设备(这里我们以 pci 总线 bdf 号为 10:00.0ssd 为例),都可以在 nvme 驱动下看到。其中,bindunbind 文件就是涉及到绑定和解绑的关键文件。


绑定与解绑

解绑一个 nvme 设备,只需将设备的 pci 总线 bdf 号写入 /sys/bus/pci/drivers/nvme(不同的设备驱动不同)/unbind 即可:

/sys/bus/pci/drivers/nvme# echo -n "0000:10:00.0" > unbind

解除绑定成功,再查看目录下文件,该驱动下不再有对应的设备。同时,/dev 下也没有对应的nvme 设备了。


绑定一个 nvme 设备,和解绑类似,将设备的 pci 总线 bdf 号写入 /sys/bus/pci/drivers/nvme(不同的设备驱动不同)/bind

/sys/bus/pci/drivers/nvme# echo -n "0000:09:00.0" > bind

绑定成功,再次展示该目录下所有文件,可以发现对应设备再次出现。


I / O

可以在目录 /sys/bus/pci/drivers/nvme 下看到很多以 PCI 设备名命名的目录,但不是说这些设备都存在于你的系统中。

以其中一个设备为例,进入 0000:10: 00.0 命名的目录。在这个目录下可以找到该设备相关的很多信息。其中 nvme/nvme0/device/resource 记录了它的 6I/O 地址区域。内容如下:

         0x00000000fbc00000 0x00000000fbc03fff 0x0000000000140204
         0x0000000000000000 0x0000000000000000 0x0000000000000000
         0x0000000000000000 0x0000000000000000 0x0000000000000000
         0x0000000000000000 0x0000000000000000 0x0000000000000000
         0x0000000000000000 0x0000000000000000 0x0000000000000000
         0x0000000000000000 0x0000000000000000 0x0000000000000000
         0x0000000000000000 0x0000000000000000 0x0000000000000000
$ cat /proc/iomem | grep '0000:10:00.0'
	fbc00000-fbc03fff : 0000:10:00.0
	
$ cat /proc/ioports | grep '0000:10:00.0'


拓扑结构

参考这里 13

树这个概念我们应该是很熟悉的了,生活周围到处都是。树的主要四部分是根、干、枝、叶。
而树根一般在地下。我们常看见的也就是以上的树干、树枝、树叶。


PCI

PCI 采用的是总线型拓扑结构。一条 PCI 总线上挂着若干个 PCI Endpoint 设备或者 PCI Bridge 设备。PCI 总线和这些设备,可以抽象为一颗 tree 上挂着各种树干、树枝、树叶。如下所示,是一个基于 PCI 的传统计算机系统,


北桥下面的那根 PCI 总线,挂载了以太网设备、SCSI 设备、南桥以及其他设备,它们共享那条总线,某个设备只有获得总线使用权才能进行数据传输。


PCIe


PCIe 采用树形拓扑结构,如上所示。
上面的拓扑图显示 RCCPU 是两个独立的部分,事实上,我们常用的处理器目前大部分都是把 CPURC 集成到了一起。

PCIe 体系架构一般包含根组件 RCRoot Complex ),交换器 Switch,终端设备 EPendpoint )等类型的 PCIe 设备组成。

  • RC 在总线架构中只有一个,用于 CPU 处理器和内存子系统与 I/O 设备之间的连接;
  • Switch 的功能通常是以软件形式提供的,它包括两个或更多的逻辑 PCI-PCI Bridge,以保持与现有 PCI 兼容,具体功能类似现在的网络交换机。

每个 PCIe 设备在系统总线上都有自己的标识符,这个标识符就是 BDFBusDeviceFunction ),PCIe 的配置软件(即 Root 的应用层,一般是 host )应当有能力识别整个 PCIe 总线系统的拓扑逻辑,以及其中的每一条总线( Bus ),每一个设备( Device )和每一项功能( Function)。

BDF 中,Bus Number 占用 8 位,Device Number 占用 5 位,Function Number 占用 3 位。
显然,PCIe 总线最多支持 256 个子总线,每个子总线最多支持 32 个设备,每个设备最多支持 8 个功能,如下图所示:

需要注意的是,每 Bus 0 总是分配给 RC,且每个设备必须要有功能 0Fun0 ),其他的7个功能( Fun1~ Fun7 )都是可选的。

可以使用 lspci -t 命令查看当前使用主机的 PCIe 拓扑结构,只不过这颗显示的树是横向的,转了90 度,如下图所示:

这是一颗倒着生长的树。树的根即是 RC ( Root Complex ),我们叫做根节点或者根联合体。
RC 内部的结构和处理机制如同我们看不到的树在地面以下的错综复杂的根。记住这里是根,是一切的开始就好。

从根节点开始向下生长出树干和树枝(PCIe 链路),某些树枝还扩展生长出旁路的分支,这些可以扩展生长的地方,对应拓扑中的 Switch

最末端的叶/果实,对应拓扑中的EP(End Point)。

一个 PCIe 设备的 ID 由以下几个部分组成:
0000:00:00.0 为例,分别对应 PCI 域,总线号,设备号,功能号。

PCI 域: PCIID,目的是为了突破 PCIe 256 条总线的限制。
PCI 总线号:PCI 设备的总线 ID,占用 8 位,所以 PCIe 总线最多支持 256 个子总线。
PCI 设备号:指定总线上,PCI 的设备 IDDevice Number 占用 5 位, 所以每个子总线最多支持 32 个设备。
PCI 功能号:指定设备上,PCI 设备的功能 ID, 一个 PCI 物理设备可以实现多个功能设备,且逻辑功能相互独立,Function Number 占用 3 位,所以每个物理设备最多支持 8 个功能。

BDF( Busdevicefunction )构成了每个 PCIe 设备节点的身份标识。


Root Complex

Root Complex ( RC ) 是树的根,它为 CPU 代言,与整个计算机系统其他部分通信,比如 CPU 通过它访问内存、通过它访问 PCIe 系统中的 EP 设备。

RC 的内部实现很复杂,PCIe Spec 也没有规定 RC 该做什么、不该做什么。可以简单将它理解为一条内部的 PCIe 总线( Bus 0 ),以及通过若干个 PCI bridge,扩展出一些 PCIe Port,如下所示,



PCIe Endpoint

PCIe Endpoint,就是 PCIe 终端设备,比如 PCIe SSDPCIe 网卡 等,这些 Endpoint 可以直接连在 RC 上,也可以通过 Switch 连到 PCIe 总线上。


Switch

Switch 用于扩展链路,提供更多的端口用以连接 Endpoint。拿 USB 打比方,计算机主板上提供的 USB 口有限,如果你要连接很多 USB 设备,比如无线网卡、无线鼠标、USB 摄像头、USB 打印机、U 盘等,USB 口不够用,我会上网买个 USB HUB 用以扩展接口。同理,如果 PCIe Port 不够,可以以 Switch 作为 PCIe Port Hub,拓展更多的接口用以连接多个 PCIe Endpoint 设备。

Switch 扩展了 PCIe 端口,靠近 RC 的那个端口,我们称为上游端口( Upstream Port ),而分出来的其他端口,我们称为下游端口( Downstream Port )。一个 Switch 只有一个上游端口,可以扩展出若干个下游端口。下游端口可以直接连接 Endpoint,也可以连接 Switch,扩展出更多的 PCIe 端口,如下所示,



对每个 Switch 来说,它下面的 Endpoint 或者 Switch,都是归它管的。上游下来的数据,它需要甄别数据是传给它下面哪个设备的,然后进行转发;下面设备向 RC 传数据,也要通过Switch 代为转发。因此,Switch 的作用就是扩展 PCIe 端口,并为挂在它上面的设备( Endpoint 或者 Switch ) 提供路由和转发服务。

​每个 Switch 内部,也是有一根内部 PCIe 总线的,然后通过若干个 Bridge,扩展出若干个下游端口,如图所示,



总结

PCIe 采用的是树形拓扑结构。

  • RC 是树的根或主干,它为 CPU 代言,与 PCIe 系统其他部分通信,一般为通信的发起者。
  • Switch 是树枝,树枝上有叶子( Endpoint ),也可节外生枝,Switch 上连 Switch,归根结底,是为了连接更多的 EndpointSwitch 为它下面的 EndpointSwitch 提供路由转发服务。Endpoint 是树叶,诸如 SSD网卡显卡 等,实现某些特定功能( Function )。我们还看到有所谓的 Bridge,用以将 PCIe 总线转换成 PCI 总线,或者反过来,不是我们要讲的重点,忽略之。
  • PCIe 与采用 总线共享式通信方式PCI 不同,PCIe 采用 点到点Endpoint to Endpoint )的通信方式,每个设备独享通道带宽,速度和效率都比 PCI 好。
    但是,虽然 PCIe 采用点到点通信,即理论上任何两个 Endpoint 都可以直接通信,但实际中很少这样做,因为两个不同设备的数据格式不一样,除非这两个设备是同一个厂商的。通常都是EndpointRC 通信,或者 Endpoint 通过 RC 与另外一个 Endpoint 通信。

问题

做底层驱动开发特别是 PCIe 驱动相关的开发,经常看到的一个错误就是 “树乱了”。

树乱了从表面上看,即是 lspci -t 的内容发生了错乱,跟正常情况下的树不一样了。
本质上,“树乱了” 由于系统中的某些 PCIe 设备出错或者异常导致的。
lspci -t 时,系统会重新访问这颗树上的所有设备,由于部分 PCIe 设备的异常,访问失败(返回异常值或者全 F),从而影响 pciutils 构建出一颗完整的树,出现部分树枝断裂 (链路 link down ),树叶错位( EP Bar 空间异常)等现象。


分层结构

PCIe 分层结构绝大多数的总线或者接口,都是采用分层实现的。PCIe 也不例外,它的层次结构如图所示,


PCIe 定义了下三层:

  • 事务层( Transaction Layer
  • 数据链路层( Data Link Layer
  • 物理层( Physical Layer,包括 逻辑子模块电气子模块

每层职能是不同的,但下层总是为上层服务的。
分层设计的一个好处是,如果层次分得够好,接口版本升级时,硬件设计可能只需要改动某一层,其他层可以保持不动。( 分层易于硬件和软件的维护和更改


数据传输

PCIe Endpoint 设备


PCIe 传输的数据从上到下,都是以数据包( Packet )的形式传输的,每层数据包都是有其固定的格式。

  • 事务层的主要职责是创建(发送)或者解析(接收)TLPTransaction Layer Packet )、流量控制、QoS、事务排序等。( TLP 创建(发送)或者解析(接收))
  • 数据链路层的主要职责是创建(发送)或者解析(接收)DLLPData Link Layer Packet )、Ack / Nak 协议(链路层检错和纠错)、流控、电源管理等。
  • 物理层的主要职责是处理所有的 Packet 数据物理传输,发送端数据分发到各个 Lane 传输( Stripe ),接收端把各个 Lane 上的数据汇总起来( De-stripe ),每个 Lane 上加扰( Scramble ,目的是让 01 分布均匀,去除信道的电磁干扰 EMI )和去扰( De-scramble ),以及 8/10 或者 128/130 编码解码等。

数据打包发送

数据从上到下,一层层打包,上层打包完的数据,作为下层的原始数据,再打包。就像人穿衣服一样,穿了内衣穿衬衫,穿了衬衫穿外套。


Data 是事务层上层(诸如命令层、NVMe 层)给的数据,

  • 事务层给它头上加个 Header,然后尾巴上再加个 CRC 校验,就构成了一个 TLP
  • 这个 TLP 下传到数据链路层,又被数据链路层在头上加了个包序列号( Sequence Number , SN),尾巴上再加个 CRC 校验,然后下传到物理层。
  • 物理层为其头上加个 Start,尾巴上加个 End 符号,把这些数据分派到各个 Lane 上,然后在每个 Lane 上加扰码,经 8 / 10128 / 130 编码,最后通过物理传输介质传输给接收方,如上图所示。

数据剥离接收

  • 接收方物理层是最先接收到这些数据的,掐头( Start )去尾( End ),然后交由上层。
  • 在数据链路层,校验序列号和 LCRC,如果没问题,剥掉序列号和 LCRC,往事务层走;如果校验出差,通知对方重传。
  • 在事务层,校验 ECRC,有错,数据抛弃;没错,去掉 ECRC,获得数据。

整个过程犹如脱衣睡觉,外套脱了,衬衫脱了,内衣也脱了,光溜溜钻进被窝,如上图所示。

PCI 数据裸奔不同,PCIe 的数据是穿衣服的。PCIe 数据以 Packet 的形式传输,比起 PCI 冷冰冰的数据,PCIe 的数据是鲜活有生命的。


Switch


每个 Endpoint 都需要实现这三层,每个 SwitchPort 也需要实现这三层。

虽说 Switch 的主要功能是转发数据,但也还需要实现事务层。因为数据的目的地信息是在 TLP 中的,如果不实现这一层,就无法知道目的地址,也就无法实现数据寻址路由。


Root Complex & Switch

如果 RC 要与 EP1 通信,中间要经历怎样的一个过程?

如果把前述的数据发送和接收过程叫作穿衣和脱衣,那么,RCEP1 数据传输过程中,则存在好几次这样穿衣脱衣的过程:

  • RC 帮数据穿好衣服,发送给 Switch 的上游端口;
  • A 为了知道该笔数据发送给谁,就需要脱掉该数据的衣服,找到里面的地址信息。衣服脱光后,Switch 发现它是往 EP1 的,又帮它换了身新衣服,发送给端口 B;
  • B 又不嫌麻烦的脱掉它的衣服,换上新衣服,最后发送给 EP1

总结一下,即 RC --> 上游端口 A --> 下游端口 B --> EP1

如下图所示:

Switch 的主要功能是转发数据,为什么还需要实现事务层?
因为 Switch 必须实现这三层,因为数据的目的地信息是在 TLP 中的,如果不实现这一层,就无法知道目的地址,也就无法实现数据寻址路由。


PCIe TLP

主机与 PCIe 设备之间,或者 PCIe 设备与设备之间,数据传输都是以 Packet 形式进行的。事务层根据上层(软件层或者应用层)请求( Request )的类型、目的地址和其他相关属性,把这些请求打包,产生 TLPTransaction Layer Packet,事务层数据包)。然后这些 TLP 往下,经历数据链路层、物理层,最终到达目标设备。


TLP 类型

根据软件层的不同请求,事务层产生四种不同的 TLP 请求:

请求类型请求用途应用场景
Memory访问内存空间一个设备的物理空间可以通过内存映射( Memory Map )的方式映射到主机内存,有些还可以映射到主机的 IO 空间(如果主机有)。
但新的 PCIe 设备只支持内存映射。

PCIe 线上主流传输的是 Memory 访问相关的 TLP,主机与设备或者设备与设备之间,数据都是在彼此的 Memory 之间(抛掉IO) 交互,因此,这种 TLP 是我们最常见的。
IO访问 IO 空间
Configuration访问配置空间所有配置空间( Configuration )的访问,都是主机发起的,确切地说是 RC 发起的,往往只在上电枚举和配置阶段会发起配置空间的访问。

这样的 TLP 很重要,但不是常态。
Message中断信息、错误信息等只有在有中断或者有错误等情况下,才会有 Message TLP,这不是常态。

前3种( MemoryIOConfiguration ) 在 PCI 或者 PCI-X 时代就有了,最后的 Message 请求是 PCIe 新加的。

PCI 或者 PCI-X 时代,像中断、错误以及电源管理相关信息,都是通过边带信号( Sideband Signal ) 进行传输的, 但 PCIe 干掉了这些边带信号线,所有的通信都是走带内信号,即通过 Packet 传输,因此,过去一些由边带信号传输的输入,比如 中断信息错误信息等,就交由 Message 来进行传输了。


Non-posted / Posted

上面提到的 TLP 类型中,

  • 需要对方响应的,我们称之为 Non-Posted TLP
  • 不指望对方给响应的,我们称之为 Posted TLP,因为对方能否收到都是个问题。

只有 Memory WriteMessage 两种 TLPPosted 的就可以了,其他都是 Non-Posted 的。

ConfigurationIO 访问,无论读写,都是 Non-Posted 的,这样的请求必须得到设备的响应;

Configuration 一栏,我们看到 Type 0Type 1。在之前的拓扑结构中,我们看到除了Endpoint 之外,还有 Switch,他们都是 PCIe 设备,但 Configuration 配置格式不同,因此用 Type 0Type 1 区分。

Memory Read 必须是 Non-Posted 的,我读你数据,你不返回数据(返回数据也是响应),那肯定不行,所以 Memory Read 必须得到响应;

Message TLPPosted 的,不需要响应;

Memory WritePosted 的,我数据传给你,无须回复。写操作失败丢数据的风险较小,数据链路层提供了 ACK/NAK 机制,一定程度上能保证 TLP 正确交互,因此能很大程度上减小了数据写失败的可能。

Non-PostedRequest ,是一定需要对方响应的,对方需要通过返回一个 Completion TLP 来作为响应。

  • Read Request 来说,响应者通过 Completion TLP 返回请求者所需的数据,这种 Completion TLP 包含有效数据;
  • Write Request( 现在只有 Configuration Write 了 )来说,响应者通过 Completion TLP 告诉请求者执行状态,这样的 Completion TLP 不含有效数据。


示例
Memory Read


PCIe 设备 C 想读主机内存的数据,因此,它在事务层上生成一个 Memory Read TLP,该 MRd 一路向上,到达 RCRC 收到该 Request,就到内存中取 PCIe 设备 C 所需的数据,RC 通过 Completion with Data TLP(CplD) 返回数据,原路返回,直到 PCIe 设备 C

一个 TLP 最多只能携带 4KB 有效数据,因此,上例中,如果 PCIe 设备 C 需要读 16KB 的数据,则 RC 必须返回 4CplDPCIe 设备 C。注意,PCIe 设备 C 只需发 1MRd 就可以了。


Memory Write


主机想往 PCIe 设备 B 中写入数据,因此 RC 在其事务层生成了一个 Memory Write TLP(要写的数据在该 TLP 中),通过 Switch 直到目的地。前面说过 Memory Write TLPPosted 的,因此,PCIe 设备 B 收到数据后,不需要返回 Completion TLP(如果这时返回 Completion TLP,反而是画蛇添足)。同样的,由于一个 TLP 只能携带 4KB 数据,因此主机想往 PCIe 设备 B 上写入 16KB 数据,RC 必须发送 4MWr TLP


TLP 结构

无论是 Request TLP 或者是作为回应的 Completion TLP,他们基本都是同一个结构,如下图所示:

TLP 主要由3部分组成:HeaderData (可选,取决于具体 TLP类型) 和 ECRC (可选)。

TLP 始于发送端的 Trasaction Layer 事务层,终于接收端的 Trasaction Layer 事务层。

  • 每个 TLP 都有一个 Header,跟动物一样,没有头就活不了,所以 TLP 可以没手没脚,但不能没有 Header。事务层根据上层请求内容,生成 TLP HeaderHeader 内容包括发送者的相关信息、目标地址(该 TLP 要发给谁)、TLP 类型(如 Memory readMemory Write 之类的)、数据长度(如果有的话)等等。
  • Data Payload 域,用以放有效载荷数据。该域不是必须的,因为并不是每个 TLP 都必须携带数据的,比如 Memory Read TLP,它只是一个请求,数据是由目标设备通过 Completion TLP 返回的。
  • ECRCEnd to End CRC )域,它对之前的 HeaderData(如果有的话)生成一个 CRC,在接收端然后根据收到的 TLP,重新生成 HeaderData(如果有的话)的 CRC,和收到的 CRC 比较,一样则说明数据在传输过程中没有出错,否则就有错。
    它也是可选的,可以设置不加 CRC

一个 TLP 最大载重是 4KB,数据长度大于 4KB 的话,就需要分几个 TLP 传输。


Header

TLP Header 存在通用部分,也存在视具体 TLP 而定的 TLP Header 的部分。


Memory TLP

对一个 PCIe 设备来说,开放给主机访问的设备空间首先会被映射到主机的内存空间。

不同的 PCIe Endpoint 设备空间会映射到主机内存空间的不同位置。

  • 目标
    如果主机想要访问设备的某个空间,TLP Header 中的地址应当设置为其在主机内存空间的映射地址。
    TLP 经过 Switch 时,Switch 会根据地址信息将其转发到目标设备。

  • Memory TLP 的源是通过 Request ID 告知的。每个设备在 PCIe 系统中都有唯一的 ID,该 ID 由总线 Bus、设备 Device、功能 Function 三者唯一确定。只需要知道一个 PCIe 组成有唯一的一个 ID,不管是 RCSwitch 或者 Endpoint

Configuration TLP

配置可以认为是一个 Endpoint 或者 Switch 的标准空间,不准哪家vendor生产都需要有这段空间且按协议放相应的内容,这段空间在初始化时需要映射到主机的内存空间。

主机软件通过 RC 按协议借助 Configuration TLPSwitchEndpoint 交互。

Configuration TLP 中的

  • TypeEndpointSwitchConfiguration 格式不太一样,分别由 Type 0Type 1 来表示;
  • Bus Number + Device + Function 就唯一决定了目标设备;
  • Ext Reg Number + Register Number 相当于配置空间的偏移。找到了目标设备,指定了配置空间的偏移,就能找到具体想访问的配置空间的某个位置(寄存器)。

Message TLP

Message TLP 用于传输中断、错误、电源管理等信息,取代 PCI 时代的边带信号传输。

Message Code 指定该 Message 的类型。


Completion TLP

对于 TLP 种类为 Non-PostedRequest TLP 才有 Completion TLP
Completion TLP 照搬 Request TLP 中的 Requester ID(发起者) 和 Tag(接收者) 的源地址。

Completion TLP 一方面可返回请求者(比如 Memory Read TLP)的数据,另一方面还可返回该事务的状态。


TLP 路由

参考这里 14

PCIe 共有 3 种路由方式:基于地址( Address )路由,基于设备 IDBus number + Device number + Function Number )路由,还有就是隐式( Implicit )路由。

不同类型的 TLP,其寻址方式也不同,下表总结了每种 TLP 对应的路由方式:

TLP 类型路由方式
Memory Read/Write TLP地址路由
Configuration Read/Write TLPID 路由
Completion TLPID 路由
Message TLP地址路由或者ID路由或者隐式路由

下面分别讲讲这几种路由方式。


地址路由

前面提到,Switch 负责路由和 TLP 的转发,而路由信息是存储在 SwitchConfiguration 空间的,因此,很有必要先理解 SwitchConfiguration


BAR0BAR1 没有什么好说,跟前一节讲的 EndpointBAR 意义一样,存放 Switch 内部空间在 Host 内存空间映射基址。

Switch 有一个上游端口( 靠近 RC )和若干个下游端口,每个端口其实是一个 Bridge,都是有一个 Configuration 的,每个 Configuration 描述了其下面连接设备空间映射的范围,分别由Memory BaseMemory Limit 来表示。

对上游端口,其 Configuration 描述的地址范围是它下游所有设备的映射空间范围。
对每个下游端口的 Configuration,描述了连接它端口设备的映射空间范围。

以下面这张图为例,RangeMemory BaseMemory Limit 限定,


  • Memory Read 或者 Memory Write TLPHeader 里面都有一个地址信息,该地址是PCIe设备内部空间在内存中的映射地址。

Endpoint


当一个 Endpoint 收到一个 Memory Read 或者 Memory Write TLP,它会把 TLP Header 中的地址跟它 Configuration 当中的所有 BAR 寄存器比较,

  • 如果 TLP Header 中的地址落在这些 BAR 的地址空间,那么它就认为该 TLP 是发给它的,于是接收该 TLP
  • 否则就忽略。

Switch

Upstream -> Downstream

当一个 Switchupstream 上游端口收到一个 Memory Read 或者 Memory Write TLP,它首先把 TLP Header 中的地址跟它自己 Configuration 当中的所有 BAR 寄存器比较,

  • 如果 TLP Header 当中的地址落在这些 BAR 的地址空间,那么它就认为该 TLP 是发给它的,于是接收该 TLP(这个过程与 Endpoint 的处理方式一样);
  • 否则,看这个地址是否落在其下游设备的地址范围内( 是否在 memory basememory limit 之间),如果是,说明该 TLP 是发给它下游设备的,因此它要完成路由转发;
    如果地址不落在下游设备的地方范围内,说明该 TLP 不是发给它下面设备的,因此不接受该TLP
Downstream -> Upstream

它首先把 TLP Header 中的地址跟它自己 Configuration 当中的所有 BAR 寄存器比较,

  • 如果 TLP Header 当中的地址落在这些 BAR 的地址空间,那么它就认为该 TLP 是发给它的,于是接收该TLP(跟前面描述一样);
  • 否则,看这个地址是否落在其下游设备的地址范围内(是否在 memory basememory limit 之间),如果是,这个时候不是接受,而是拒绝;相反,如果地址不落在下游设备的地方范围内,Switch 则把该 TLP 传上去。

ID 路由

在一个 PCIe 拓扑结构中,由 ID = Bus number+Device number+Function NumberBDF ) 能唯一找到某个设备的某个功能。这种按设备 ID 号来寻址的方式叫做 ID 路由。

使用 ID 路由的 TLP,其 TLP Header 中含有 BDF 信息:



Endpoint

当一个 Endpoint 收到一个这样的 TLP,它用自己的 ID 和收到 TLP Header 中的 BDF 比较,如果是给自己的,就收下 TLP,否则就拒绝。


Switch

如果是一个 Switch 收到这样的一个 TLP,怎么处理?我们再回头去看看 SwitchConfiguration Header


看三个寄存器:Subordinate Bus NumberSecondary Bus NumberPrimary Bus Number,看下图就知道这几个寄存器是什么意思:


对一个 Switch 来说,

  • 每个 Port 靠近 RC( 上游 )的那根 Bus 叫做 Primary Bus,其 Number 写在其 Configuration Header 中的 Primary Bus Number 寄存器;
    对上游端口,Subordinate Bus 是其下游所有端口连接的 Bus 编号最大的那个 BusSubordinate Bus Number 写在每个 PortConfiguration Header 中的 Subordinate Bus Number 寄存器。
  • 每个 Port 下面的那根 Bus 叫做 Secondary Bus,其 Number 写在其 Configuration Header 中的Secondary Bus Number 寄存器;


当一个 Switch 收到一个基于 ID 寻址的 TLP,如上所示:

  • 检查 TLP 中的 BDF 是否与自己的 ID 匹配,如匹配,说明该 TLP 是给自己的,收下;
  • 否则,则检查该 TLP 中的 Bus Number 是否落在 Secondary Bus NumberSubordinate Bus Number 之间,如果是,说明该 TLP 是发给其下游设备的,然后转发到对应的下游端口;
    如果其他情况,则拒绝这些 TLP

隐式路由

Message TLPHeader 总是 4DW,如下图所示:


Type 字段,低三位,用 rrr 表示的,指明该 Message 的路由方式,具体如下:


只有 Message TLP 才支持隐式路由。在 PCIe 总线中,有些 Message 是与 RC 通信的,RC 是该 TLP 的发送者或者接收者,因此没有必要明明白白的指定地址或者 ID,而是采用 ”你懂的” 的方式进行路由,这种路由方式为隐式路由。

除此之外,Message TLP 还支持地址路由和 ID 路由,但以隐式路由为主。

以下主要讨论 Message 使用隐式路由的情况。
如果是地址路由或者 ID 路由,Message TLP 的路由跟别的 TLP 一样,不赘述。


Endpoint

当一个 Endpoint 收到一个 Message TLP,检查 TLP Header,如果是 RC 的广播 Message(011b) 或者该 Message 终结于它( 100b ),它就接受该 Message


Switch

当一个 Switch 收到一个 Message TLP,检查 TLP Header,如果是 RC 的广播 Message(011b),则往它每个下游端口复制该 Message 然后转发;如果该 Message 终结于它 (100b),则接受该 TLP;如果下游端口收到发给 RCmessage,往上游端口转发便是。


PCIe 配置和地址空间

参考这里 8


概念

PCIe3 个相互独立的物理地址空间:设备存储器地址空间、I/O 地址空间和配置空间。

配置空间是 PCIe 所特有的一个按协议分配内容的物理空间。由于 PCIe 支持设备即插即用,所以 PCIe 设备不占用固定的内存地址空间或 I/O 地址空间,而是通过配置空间来实现地址映射的。
主机软件可以通过读取配置空间获得该设备的一些信息,也可以通过它来配置该设备。

系统加电时,BIOS 检测 PCIe 总线,确定所有连接在 PCIe 总线上的设备以及它们的配置要求,并进行系统配置。
所以,所有的 PCIe 设备必须实现配置空间,从而能够实现参数的自动配置,实现真正的即插即用。


PCI

PCIPCI-X 时代就有配置空间的概念。那时的配置空间结构如下,

PCI 总线规范定义的配置空间总长度为 256 个字节,配置信息按一定的顺序和大小依次存放。

64 个字节的配置空间称为配置头,一般有两种,Type0Type1,分别对应 EP 设备和 设备。配置头的主要功能是用来识别设备、定义主机访问 PCI 卡的方式( I/O 访问或者存储器访问,还有中断信息)。

192 个字节是 Capability,是设备告诉 Host 它有多牛逼,都会什么绝活。


PCIe

进入 PCIe 时代,PCIe 能耐更大,192 Bytes 不足以罗列它的绝活。为了保持后向兼容,又要不把绝活落下,于是扩展后者的空间,整个配置空间由 256 Bytes 扩展成 4KB,前面 256 Bytes 保持不变:


配置空间结构

PCIe Header (0 ~ 64 bytes)

  • Device IDVendor IDClass CodeRevision ID,是只读寄存器,PCIe 设备通过这些寄存器告诉 Host 软件,这是哪个厂家的设备、设备 ID 是多少、以及是什么类型的设备。
  • Endpoint ConfigurationType 0 ),提供了最多 6BARBase Address Register ),而对 SwitchType 1 )来说,只有 2 个。
    BAR 是干什么用的?
    每个 PCIe 设备,都有自己的内部空间,这部分空间如果开放给 Host( 软件或者 CPU ) 访问,那么 Host 怎样才能往这部分空间写入数据,或者读数据呢?
    CPU 只能直接访问 Host 内存( Memory )空间( 或者 IO 空间,我们不考虑 ),不对 PCIe 等外设直接操作。
    CPU 如果想访问某个 PCIe 设备的配置空间,它不能亲自跟 PCIe 外设打交道,因此先叫 RC 通过 TLP 把数据从 PCIe 外设读到 Host 内存,然后 CPUHost 内存读数据;如果要往 PCIe 设备写数据,则先把数据在内存中准备好,然后叫 RC 通过 TLP 写入到 PCIe 设备。


上图例子中,最左边虚线的表示 CPU 要读 Endpoint A 的数据,RC 则通过 TLP(经历Switch )数据交互获得数据,并把它写入到系统内存中,然后 CPU 从内存中读取数据(紫色箭头所示),从而 CPU 间接完成对 PCIe 设备数据的读取。

具体实现就是上电的时候,系统把 PCIe 设备开放的空间映射到内存空间,CPU 要访问该 PCIe 设备空间,只需访问对应的内存空间。RC 检查该内存地址,如果发现该内存空间地址是某个 PCIe 设备空间的映射,就会触发其产生 TLP,去访问对应的 PCIe 设备,读取或者写入PCIe 设备。

一个 PCIe 设备,可能有若干个内部空间(属性可能不一样,比如有些可预读,有些不可预读)需要映射到内存空间,设备出厂时,这些空间的大小和属性都写在 Configuration BAR 寄存器里面,然后上电后,系统软件读取这些 BAR,分别为其分配对应的系统内存空间,并把相应的内存基地址写回到 BAR。( BAR 的地址其实是 PCI 总线域的地址,CPU 访问的是存储器域的地址,CPU 访问 PCIe 设备时,需要把总线域地址转换成存储器域的地址。)


如上图例子,一个 Native PCIe Endpoint,只支持 Memory Map,它有两个不同属性的内部空间要开放给系统软件,因此,它可以分别映射到系统内存空间的两个地方。

还有一个 Legacy Endpoint,它既支持 Memory Map,还支持 IO Map,它也有两个不同属性的内部空间,分别映射到系统内存空间和 IO 空间。

来个例子,看一下一个 PCIe 设备,系统软件是如何为其分配映射空间的,


上电时,系统软件首先会读取 PCIe 设备的 BAR0,得到数据:


然后系统软件往该 BAR0 写入全 1,得到:


BAR 寄存器有些 bit 是只读的,是 PCIe 设备在出厂前就固定好的 bit,写全 1 进去,如果值保持不变,就说明这些 bit 是厂家固化好的,这些固化好的 bit 提供了这块内部空间的一些信息:
12 没变,表明该设备空间大小是 4KB212 次方),然后低 4 位表明了该存储空间的一些属性( IO 映射还是内存映射,32 bit 地址还是 64 bit 地址,能否预取?做过单片机的人可能知道,有些寄存器只要一读,数据就会清掉,因此,对这样的空间,是不能预读的,因为预读会改变原来的值),这些都是 PCIe 设备在出厂前都设置好的提供给系统软件的信息。


然后系统软件根据这些信息,在系统内存空间找到这样一块地方来映射这 4KB 的空间,把分配的基地址写入到 BAR0,如上所示,从而最终完成了该 PCIe 空间的映射。

一个 PCIe 设备可能有若干个内部空间需要开放出来,系统软件依次读取 BAR1BAR2 … 直到 BAR5,完成所有内部空间的映射。

上面主要讲了 EndpointBAR


PCIe Capability (64 ~ 256bytes)

这段空间主要存放一些与 MSI / MSI-X 中断机制和电源管理相关的 capbility
在内核 include/uapi/linux/pci_regs.h 归纳了 Capability ID 和各个 Capability 名称的对应关系:

/* Capability lists */
#define PCI_CAP_LIST_ID   	0 /* Capability ID */
#define  PCI_CAP_ID_PM    	0x01 /* Power Management */
#define  PCI_CAP_ID_AGP   	0x02 /* Accelerated Graphics Port */
#define  PCI_CAP_ID_VPD   	0x03 /* Vital Product Data */
#define  PCI_CAP_ID_SLOTID  0x04 /* Slot Identification */
#define  PCI_CAP_ID_MSI   	0x05 /* Message Signalled Interrupts */
#define  PCI_CAP_ID_CHSWP 	0x06  /* CompactPCI HotSwap */
#define  PCI_CAP_ID_PCIX  	0x07 /* PCI-X */
#define  PCI_CAP_ID_HT    	0x08 /* HyperTransport */
#define  PCI_CAP_ID_VNDR  	0x09 /* Vendor specific */
#define  PCI_CAP_ID_DBG   	0x0A /* Debug port */
#define  PCI_CAP_ID_CCRC  	0x0B /* CompactPCI Central Resource Control */
#define  PCI_CAP_ID_SHPC  	0x0C /* PCI Standard Hot-Plug Controller */
#define  PCI_CAP_ID_SSVID 	0x0D  /* Bridge subsystem vendor/device ID */
#define  PCI_CAP_ID_AGP3  	0x0E /* AGP Target PCI-PCI bridge */
#define  PCI_CAP_ID_EXP   	0x10 /* PCI Express */
#define  PCI_CAP_ID_MSIX  	0x11 /* MSI-X */
#define  PCI_CAP_ID_AF    	0x13 /* PCI Advanced Features */
#define PCI_CAP_LIST_NEXT 	1 /*Next capability in the list */
#define PCI_CAP_FLAGS   	2 /* Capability defined flags (16 bits) */
#define PCI_CAP_SIZEOF    	4

PCIe 扩展配置空间 (256 ~ 4K bytes)

这段空间主要存放 PCIe 独有的一些 Capbility 结构,如 AERSR-IOV 等。
PCIe 扩展能力定义如下:

/* Extended Capabilities (PCI-X 2.0 and Express) */
#define PCI_EXT_CAP_ID(header)          (header & 0x0000ffff)
#define PCI_EXT_CAP_VER(header)         ((header >> 16) & 0xf)
#define PCI_EXT_CAP_NEXT(header)        ((header >> 20) & 0xffc)

#define PCI_EXT_CAP_ID_ERR      0x01    /* Advanced Error Reporting */
#define PCI_EXT_CAP_ID_VC       0x02    /* Virtual Channel Capability */
#define PCI_EXT_CAP_ID_DSN      0x03    /* Device Serial Number */
#define PCI_EXT_CAP_ID_PWR      0x04    /* Power Budgeting */
#define PCI_EXT_CAP_ID_RCLD     0x05    /* Root Complex Link Declaration */
#define PCI_EXT_CAP_ID_RCILC    0x06    /* Root Complex Internal Link Control */
#define PCI_EXT_CAP_ID_RCEC     0x07    /* Root Complex Event Collector */
#define PCI_EXT_CAP_ID_MFVC     0x08    /* Multi-Function VC Capability */
#define PCI_EXT_CAP_ID_VC9      0x09    /* same as _VC */
#define PCI_EXT_CAP_ID_RCRB     0x0A    /* Root Complex RB? */
#define PCI_EXT_CAP_ID_VNDR     0x0B    /* Vendor-Specific */
#define PCI_EXT_CAP_ID_CAC      0x0C    /* Config Access - obsolete */
#define PCI_EXT_CAP_ID_ACS      0x0D    /* Access Control Services */
#define PCI_EXT_CAP_ID_ARI      0x0E    /* Alternate Routing ID */
#define PCI_EXT_CAP_ID_ATS      0x0F    /* Address Translation Services */
#define PCI_EXT_CAP_ID_SRIOV    0x10    /* Single Root I/O Virtualization */
#define PCI_EXT_CAP_ID_MRIOV    0x11    /* Multi Root I/O Virtualization */
#define PCI_EXT_CAP_ID_MCAST    0x12    /* Multicast */
#define PCI_EXT_CAP_ID_PRI      0x13    /* Page Request Interface */
#define PCI_EXT_CAP_ID_AMD_XXX  0x14    /* Reserved for AMD */
#define PCI_EXT_CAP_ID_REBAR    0x15    /* Resizable BAR */
#define PCI_EXT_CAP_ID_DPA      0x16    /* Dynamic Power Allocation */
#define PCI_EXT_CAP_ID_TPH      0x17    /* TPH Requester */
#define PCI_EXT_CAP_ID_LTR      0x18    /* Latency Tolerance Reporting */
#define PCI_EXT_CAP_ID_SECPCI   0x19    /* Secondary PCIe Capability */
#define PCI_EXT_CAP_ID_PMUX     0x1A    /* Protocol Multiplexing */
#define PCI_EXT_CAP_ID_PASID    0x1B    /* Process Address Space ID */
#define PCI_EXT_CAP_ID_DPC      0x1D    /* Downstream Port Containment */
#define PCI_EXT_CAP_ID_L1SS     0x1E    /* L1 PM Substates */
#define PCI_EXT_CAP_ID_PTM      0x1F    /* Precision Time Measurement */
#define PCI_EXT_CAP_ID_MAX      PCI_EXT_CAP_ID_PTM

#define PCI_EXT_CAP_DSN_SIZEOF  12
#define PCI_EXT_CAP_MCAST_ENDPOINT_SIZEOF 40

配置空间访问

前面说每个 PCIe 设备都有一个配置空间,但其实每个 PCIe 设备至少有 1 个配置空间。
一个PCIe 设备,它可能具有多个功能( function ),比如既能当硬盘,还能当网卡。
每个功能对应一个独立的配置空间。

在一个 PCIe 拓扑结构里,一条总线下面可以挂几个设备,而每个设备可以具有几个功能,如下所示:

因此,在整个 PCIe 系统中,只要知道了 Bus+Device+Function,就能找到对应的 Function

寻址基本单元是功能( function ),它的 ID 就由 Bus+Device+Function 组成 ( BDF )。

一个PCIe 系统,可以最多有 256Bus,每条 Bus 上可以挂最多 32Device,而每个Device 最多又能实现 8Function,而每个 Function 对应着 4KB 的配置空间。
上电的时候,这些配置空间都是需要映射到 Host 的内存空间,因此,需要占用内存空间是:256*32*8*4KB =256MB
完成映射后,CPU 发出的地址如果落在范围内,根据对应的 BDF 就可以访问到对应 EP 的配置空间了。Linux 内核中的 MCFG 实现读 / 写分别对应函数 pci_read_config_byte/word/dwordpci_write_config_byte/word/dword

EXPORT_SYMBOL(pci_bus_read_config_byte);
EXPORT_SYMBOL(pci_bus_read_config_word);
EXPORT_SYMBOL(pci_bus_read_config_dword);
EXPORT_SYMBOL(pci_bus_write_config_byte);
EXPORT_SYMBOL(pci_bus_write_config_word);
EXPORT_SYMBOL(pci_bus_write_config_dword);

系统软件是如何读取 Configuration 空间呢?
不能通过 BAR 中的地址,为什么?
别忘了 BAR 是在 Configuration 中的,你首先要读取 Configuration,才能得到 BAR。系统软件想访问哪个 Configuration,只需指定相应 Function 对应的内存空间地址,RC 发现这个地址是Configuration 映射空间,就会产生相应的 Configuration Read TLP 去获得相应 FunctionConfiguration

再回想一下前面介绍的 Configuration Read TLPHeader 格式:


Bus Number + Device + Function 就唯一决定了目标设备;
Ext Reg Number + Register Number 相当于配置空间的偏移。
找到了设备,然后指定了配置空间的偏移,就能找到具体想访问的配置空间的某个位置。

强调一下,只有 RC 才能发起 Configuration 的访问请求,其他设备是不允许对别的设备进行 Configuration 读写的。


数据链路层

参考这里 15

数据链路层位于事务层的下一层,其作用有:

  • 保证 TLP 在数据总线上的正常传输,并使用握手协议( Ack / Nak)和重传( Retry )机制来保证数据传输的一致性和完整性。

一个 TLP 源于发送端的事务层,途径发送端的数据链路层和物理层、接收端的物理层和数据链路层,终于接收端的事务层。

  • 发送端的数据链路层,接收其事务层传来的 TLP,为每个 TLP 加上 Sequence Number(序列号)和 LCRCLink CRC),而后转交物理层。
  • 接收端的数据链路层,接收其物理层传来的 TLP,检测 CRC 和序列号。
    如果有问题,会拒绝接收该 TLP,不会传至其事务层,并通知发送端重传;
    如果没问题,去除 Sequence NumberLCRC,而后转交事务层,并通知发送端 TLP 已正确接收。
  • TLP 流量控制。
  • 电源管理功能。


DLLPData Link Layer Packet,数据链路层的数据包 )源于发送端的数据链路层,终于接收端的数据链路层。因此,处于高层的事务层是感知不到它的存在的。

  • 发送端的数据链路层生成 DLLP,交由发送端的物理层,发送端的物理层加起始( SDP )和结束标志( GEN 1/2ENDGEN3 则没有 ),然后传输给对方;
  • 接收端的物理层对 DLLP 掐头去尾,交由接收端的数据链路层,接收端的数据链路层对 DLLP 进行校验。不管正确与否,DLLP 都终于这层。

一个 TLP 可以翻山越岭( 经过若干个 Switch),从一个设备传输到相隔很远的设备。

TLP 传输不同,DLLP 不需要包含路由信息(由谁发起、发送至哪里),因为 DLLP 的传输仅限于相邻的两个端口。

数据链路层主要有四大类型 DLLP

  • 确保 TLP 传输的一致性和完整性的 DLLPACK / NAK
  • 流控相关的 DLLP
  • 电源管理相关的 DLLP
  • 厂家自定义 DLLP

不同类型的 DLLP 格式相同,内容不一样。


ACK / NAK 协议

  1. 发送端的数据链路层为将要发送的 TLP 加上 Sequence NumberLCRC,在 TLP 被接收端正确收到之前,它会一直保持在一个 Replay Buffer 中。
  2. 接收端的数据链路层收到 TLP 后做 CRC 校验和 Sequence Number 检查:
  • 如果没有问题,TLP 接收端会生成和发送 ACK DLLPTLP 发送端接收到 ACK 后知道 TLP 被正确接收,而后从 Replay Buffer 中清除。TLP 接收端会去掉 Sequence NumberLCRC 并把 TLP 交由接收端事务层。
  • 如果检测到问题,TLP 接收端会生成和发送 NAK DLLPTLP 发送端收到 NAK 后知道 TLP 出错,会重新发送 Replay Buffer 中的 TLP 给到对方。

TLP 传输出错往往是瞬态的,重传基本能保证 TLP 传输正确。


TLP 流控(流量控制,Flow Control)

Flow Control 即流量控制,这一概念起源于网络通信中。

PCIe 总线采用 Flow Control 的目的是,保证发送端的 PCIe 设备永远不会发送接收端的 PCIe 设备不能接收的 TLP(事务层包),换句话说,发送端在发送前可以通过 Flow Control 机制知道接收端能否接收即将发送的 TLP

PCI 总线中,并没有 Flow Control 这样的机制,因此发送端并不知道当前时刻接收端能否接收对应的 TLP。因此,发送端只能先尝试发送直至对方接收,期间可能会被插入多个等待周期(接收设备尚未就绪等原因),甚至是重发( Retries )等。这会在一定程度上影响通信效率。

PCIe Spec 规定,PCIe 设备的每一个端口( Ports )都必须支持 Flow Control 机制,在发送TLP 之前,Flow Control 必须先检查接收端口是否有足够的 Buffer 空间来接收这个 TLP。当PCIe 设备支持多个 VCVirtual Channel )时,Flow Control 机制可以显著地提高总线的传输效率。

PCIe Spec 规定,每个 PCIe 端口最多支持 8VC,并且每个 VCFlow Control Buffer 是完全独立的。也就是说,某一个 VCFlow Control Buffer 满了,并不会影响其他的 VC 的通信。

Flow Control 机制是通过相邻两个端口( Ports )的数据链路层之间发送 DLLPFlow Control DLLPs )来实现的。也就是说 Flow Control 是一种点到点( Point to Point )的方式,而非端到端( End to End )。在进行初始化的时候,接收端需要向发送端报告(reports)其 Buffer 的大小,在正常运行状态(Run-time)时,会周期性地通过 Flow Control DLLPs 来告知发送端,接收端的各个 Buffer 的大小。

需要注意的是,虽然 Flow Control DLLP 只在相邻的数据链路层之间传输,但是相关的 Buffer 和计数器( FC Counter )确是在事务层( Transaction Layer )的,即事务层参与了 Flow Control机制的管理。如下图所示:



电源管理

暂时略过。


物理层

参考这里 16


先回顾一下 PCIeLayer 的分层结构,如上所示。

PCIe 的三层,从上到下,依次为事务层、数据链路层和物理层。每层都有自己的数据包定义:

  • 事务层产生 TLP,用以传输应用层或命令层(事务层的上层)的数据,经过数据链路层和物理层传输给接收方;
  • 数据链路层产生 DLLP,用以 ACK / NAK、流控制和电源管理,经过物理层传输到对方;
  • 物理层,不仅仅为上层 TLPDLLP 做嫁衣,也有自己的用于管理链路的数据包 Ordered SetsOS ),比如链路训练( Link Training )、改变链路电源状态等。

PCIe 中的物理层主要完成编解码( 8b/10b for Gen1 & Gen2128b/130b for Gen3 and later)、扰码与解扰码、串并转换、差分发送与接收、链路训练等功能。
其中链路训练主要通过物理层包 Ordered Sets 来实现。


物理层分为两个部分——逻辑子层和电气子层,如上图所示。

电气子层方面,PCIe 采用串行总线传输数据,使用的是差分信号,即用两根信号线上的电平差表示 01。与单端信号传输相比,差分信号抗干扰能力强,可以用更快的速度进行数据传输,从而提供更宽的带宽。

假设用2个信号线上电平差表示 01,差值大于 0,表示 1,反之表示 0
如果传输过程中存在干扰,2个信号线上加了近乎同样大小的干扰电平,若二者相减,差之几乎不变,并不会影响信号传输。

假设用单端信号传输,就很容易受感染。比如,0~1 V 表示0,1~3 V 表示 1。一个本来是 0.8 V 的电压,加入干扰,变成 1.5 V,则从 0 变为 1,则数据出错。

PCIe 物理层实现了一对收发差分对,因此可以实现全双工的通信方式。
需要注意的是,PCIe Spec 只是规定了物理层需要实现的功能、性能与参数等,至于如何实现这些却并没有明确的说明。也就是说,厂商可以根据自己的需要和实际情况,来设计 PCIe 的物理层。


逻辑子层

下面将以 Mindshare 书中的例子来简要的介绍 PCIe 的物理层逻辑部分,可能会与其他的厂商的设备的物理层实现方式有所差异,但是设计的目标和最终的功能是基本一致的。

  • 发送端

物理层逻辑子层的发送端部分的结构图如下图所示:


在进行 8b/10b 编码之前,Mux 会对来自数据链路层的数据中插入一些内容,如用于标记包边界或者 Ordered Sets 的控制字符和数据字符。为了区分这些字符,Mux 为其对应上一个 D/K# 位( Data or Kontrol )。

注:图中还包含了 Gen3 的一些实现,不过这里只介绍 Gen1 & Gen2,并不会介绍 Gen3。如果大家感兴趣的,可以去阅读 Mindshare 的书籍或者参考 PCIe Gen3Spec

Byte Striping 将来自 Mux 的并行数据按照一定的规则分配到各个 Lane 上去。随后进行扰码( Scrambler )、8b / 10b 编码、串行化( Serializer ),然后是差分发送对。

其中扰码器( Scrambler )是基于伪随机码( Pesudo-Random )的异或逻辑( XOR ),由于是伪随机码,所以只要发送端和接收端采用相同的算法和种子,接收端便可以轻松地恢复出数据。
但是,如果发送端和接收端由于某些原因导致其节拍不一致了,此时便会产生错误,因此 Gen1Gen2 的扰码器( Scrambler )会周期性地被复位。

  • 接收端

物理层逻辑子层的接收端部分的结构图如下图所示:


由于 PCIe 采用的是一种 Embeded Clock(借助 8b / 10b )机制,因此接收端在接收到数据流时,首先要从中恢复出时钟信号,这正是通过 CDR 逻辑来实现的。

如上图所示,接收端的逻辑基本上都是与发送端相对应的相反的操作。


PCIe Reset

参考这里 17

PCIe Spec 中跟 Reset 相关的术语有 Cold ResetWarm ResetHot ResetConventional ResetFunctional Level ResetFundamental ResetNon-Fundamental Reset

这些 Reset 之间是从属关系。


种类

Reset Conventional Reset Functional Level Reset Fundamental Reset Non-Fundamental Reset Cold Reset Warm Reset Hot Reset

总线规定了两个复位方式,如上图所示:

  • Conventional Reset
  1. Fundamental Reset
    Fundamental Reset 是基于边带信号 PEREST# 的,包括 ColdWarm Reset 方式,可以用 PCIe 将设备中绝大多数内部寄存器和内部状态都恢复成初始值。
  2. Non-Fundamental Reset
    Hot Reset 方式。
  • Functional Level Reset
    其中 Functional Level ResetPCIe Spec V2.0 加入的功能,因此一般把另外三种复位统称为传统的复位方式( Conventional Reset )。

参考这里 18

A cold reset is a fundamental reset that takes place after power is applied to a PCIe device. There appears to be no standard way of triggering a cold reset, save for turning the system off and back on again. On my machines, the /sys/bus/pci/slots directory is empty.

A warm reset is a fundamental reset that is triggered without disconnecting power from the device. There appears to be no standard way of triggering a warm reset.

A hot reset is a conventional reset that is triggered across a PCI express link. A hot reset is triggered either when a link is forced into electrical idle or by sending TS1 and TS2 ordered sets with the hot reset bit set. Software can initiate a hot reset by setting and then clearing the secondary bus reset bit in the bridge control register in the PCI configuration space of the bridge port upstream of the device.

A function-level reset ( FLR ) is a reset that affects only a single function of a PCI express device. It must not reset the entire PCIe device. Implementing function-level resets is not required by the PCIe specification. A function-level reset is initiated by setting the initiate function-level reset bit in the function’s device control register in the PCI express capability structure in the PCI configuration space.


功能、实现方式及对设备的影响

Fundamental Reset
功能及对设备的影响

Fundamental Reset 由硬件自动处理,会复位整个 PCIe 设备,初始化所有与状态机相关的硬件逻辑,端口状态以及配置空间中的配置寄存器等等。

但是,也有一个例外,就是 Sticky(不受复位影响)的概念。这里指的不受复位影响的前提是,PCIe 设备的电源并未被完全切断。Sticky 这一功能有助于系统定位错误与分析错误起因。

这些 FieldDebug 时会非常有用,特别是那些需要 Reset Link 的情况,如果在 Link Reset 后还能保存之前的错误状态,这对 FW 以及上层应用来说是很有用的。

Fundamental Reset 一般发生在整个系统 Reset 的时候(比如重启电脑),但也可以针对某个设备做 Fundamental Reset

Fundamental Reset 有两种,

  • Cold Reset
    指的是因为主电源断开后重新连接导致的复位。需要注意的是,即使主电源断开了,如果 PCIe 设备仍有辅助电源 Vaux 为其供电,该复位仍不会影响到 Stickybits
  • Warm Reset
    可选的,指的是在不关闭主电源的情况下,由系统触发产生的复位,比如改变系统的电源管理状态可能会触发,PCIe 协议没有定义具体如何触发 Warm Reset,而是把决定权都交给系统。

实现方式

PCIe Spec 允许两种实现 Fundamental Reset 的方式:

  • 直接通过边带信号 PERST#PCI Express Reset );
  • 不使用边带信号 PERST#PCIe 设备在主电源被切断时,自行产生一个复位信号。

PERST#


如果这块 PCIe 设备支持 PERST# 信号:

  1. 一个系统上电时,主电源稳定后会有 Power Good 信号;
  2. ICHIO控制器集线器 )发 PERST# 信号给下方悬挂的 PCIe Endpoint 设备
  3. 如果系统重启,Power Good 的信号变化会触发 PERST#ASSERTDEASSERT,就可以实现 PCIe 设备的 Cold Reset
  4. 如果系统可以提供 Power Good 信号以外的方法触发 PERST#,就可以实现 Warm ResetPERST# 信号会发送给所有的 PCIe 设备,设备可以选择这个信号,也可以选择不理;

复位信号

如果这块 PCIe 设备不支持 PERST# 信号:

  1. 一个系统上电后会自动进入 Fundamental Reset
  2. 必须能自己触发 Fundamental Reset,比如,侦测到 3.3 V 后就触发 Reset (当设备发现供电超过其标准电压时必须触发 Reset)。

Non-Fundamental Reset
实现

Hot Reset 是一种 In-band 复位,其并不使用边带信号。
PCIe 设备通过向其链路( Link )相邻的设备发送数个 TS1 Ordered Set(其中第五个字符的bit01 ),如下图所示。这些 TS1 OS 在所有的通道( Lane )上同时发送,并持续 2 ms 左右。

  • 如果 SwitchUpstream 端口收到了 Warm Reset,则会将其广播至所有的Downstream 端口,并复位其自己。
  • 如果 PCIe 设备的 Downstream 端口接收到 Warm Reset,则只需要复位其自己即可。
    PCIe 设备接收到 Warm Reset 后,LTSSM 会进入 Recovery and Hot Reset 状态,然后返回值Detect 状态,并重新开始链路初始化训练。其该 PCIe 设备的所有状态机,硬件逻辑,端口状态和配置空间中的寄存器(除了 Sticky bits )都将被初始化值默认状态。

软件可以通过向桥设备的,特定端口的配置空间中的二级总线复位( Secondary Bus Resetbit先写 0 再写 1,来产生热复位,如下图所示:


需要注意的是,如果软件设置的是 SwitchUpstream 端口的二级总线复位 bit,则该 Switch 会往其所有的 Downstream 端口广播 Hot Reset 信号。
PCIe-to-PCI 桥则会将接收到的热复位信号转换为 PRST# 置位,发送给 PCI 设备。

二级总线复位( Secondary Bus Resetbit 在配置空间的位置如下图所示:

PCIe Spec 还允许软件禁止某个链路( Link ),强制使其进入电气空闲状态( Electrical Idle )。如果将某个链路禁止,则该链路所有的下游 PCIe 设备都将收到链路禁止信号(通过TS1 OS,如下图所示),



Functional Level Reset

参考这里 19

PCIe 总线自 V2.0 加入了功能层复位( Function Level ResetFLR)的功能。该功能主要针对的是支持多个功能的 PCIe 设备( Multi-Fun PCIe Device ),可以实现只对特定的 Function 复位,而其他的 Function 不受影响。

当然,该功能是可选的,并非强制的,软件可以通过查询配置空间中的设备功能寄存器( Device Capability Register )来查询该 PCIe 设备是否支持 FLR。如下图所示:


并可以通过设备控制寄存器( Device Control Register )中的将 Initiate Function Level Reset bit1 ,如下所示,来产生 FLR



功能及对设备的影响

FLR 只复位对应 Function 的内部状态和寄存器(使其暂时不变化,Making it quiescent ),但是不影响以下寄存器:

  • Sticky bits

Cold ResetWarm Reset 也不可以复位它们。

  • HWInit 类型的寄存器

PCIe 设备中,有效配置寄存器的属性为 HWInit,这些寄存器的值由芯片的配置引脚决定,其在上电复位后从 EEPROM 中获取。
Cold ResetWarm Reset 缺可以复位这些寄存器,并从 EEPROM 中重新获取数据。

  • 链路专用寄存器,比如Captured PowerASPM ControlMax Payload Size 以及 VC 等寄存器)。
  • LTSSM 状态

实现方式

PCIe Spec 明确 FLR 的完成时间应在 100 ms 以内。

  • FLR 开始前:
  • Command Register 清空
  • 软件在启动 FLR 前应注意是否还有没完成的 CplD
    如果有,等这些 CplD 完成( 轮询 Device Status Registerbit5 直到被 clear,说明已无未完成的 CplD )再开始 FLR,或者,启动 FLR 后等 100 ms 再初始化这个 Function

这个情况如果没处理好,可能会导致 Data Corruption —— 前一批事务要求的数据因 FLR 的影响被误传给了后一批事务。

  • 确保其他软件在 FLR 期间不会访问这个 Function

  • FLR 进行中:

  • Function 对外无法使用

  • 不保留之前的任何可以被读取的信息(比如内部的 Memory 需要被清零或者改写 )

  • 回复要求 FLRCfg Request,并开始 FLR

  • 对于发进来的 TLP 回复 Unexpected Completion 或者直接丢弃

  • FLR 应该在 100 ms 内完成,但其后的初始化还需要一些时间。在初始化过程中如果收到 Cfg Request,可以回复 Configuration Retry Status

  • FLR 完成后:

  • FLR 退出后,在 20 ms 内开始 Link Training

  • 软件给 Link 充分的时间完成 Link Training 和初始化,至少要等上 100 ms 才能开始发送 Cfg Request。如果等了 100 ms 开始发 Cfg Request,但设备还没初始化完成,设备会回复 Configuration Retry Status。这时 RC 可以选择重发 Cfg Request 或者上报 CPU 说设备还未准备好。

  • 设备最多可以有 1 s 时间来重新配置 FunctionEnable,之后必须能正常工作,否则 System / RC 则可以认为设备挂了。


PCIe Max Payload Size 和 Max Read Request Size

参考这里 20

PCIe Max Payload SizePCIe Max Read Request Size 都在 Device Control Register 里,分别由 bit[14:12]bit[7:5] 控制,如下所示:



PCIe Max Payload Size

概念

PCIe 设备是以 TLP 的形式发送报文的,而 PCIe Max Payload Size ( 简称 MPS ) 决定了 PCIe 设备实际使用的 TLP 能够传输的最大字节数。

MPS 的大小是由 PCIe 链路两端的设备协商决定的。PCIe 协议允许一个最大的 Payload 可达 4 KB,但是规定在整个传输路径上的所有设备都必须使用相同的 MPS 设置,同时不能超过该路径上任何一个设备的 MPS 能力值。即,MPS Capability 高的设备要迁就低的设备。

例如,将一块 PCIe SSD 插入到一块老掉牙的主板( MPS 128 Byte)上,无论 SSDPayload Size 再大,都是没用的。

接收端必须能处理跟 MPS 设定大小相同的 TLP 数据包。
发送端不允许创建超过 MPS 设定的 TLP 数据包。

以主板上的 PCIe RCPCIe SSD 为例,它们各自在 Device Capability Register 中声明自己支持的各种 MPSOSPCIe 驱动侦测到它们各自的能力值并挑选最低的那个设置到两者的 Device Control Register 中。
PCIe SSD 自身的 MPS Capability 则是在其 PCIe core 初始化阶段设置的,


作用

MPS 的大小影响 PCIe 设备的传输效率。

  1. 对于比较大的数据量,如果 MPS 设置比较小,那么数据只能被分割成多个 TLP 进行发送,势必会影响 PCIe 链路带宽的利用率。
    MPS 的值也不是越大越好,一方面 MPS 设置的越大,硬件处理数据包所需的内存和逻辑量也随之增加; 另一方面,MPS 的值是一个自协商的结果,当前市场上支持较大 MPS 值的 PCIe Endpoint 设备不常见,所以没有必要设置本端的 MPS 值太大。
  2. 不合理的 MPS 设置会导致数据通信时上报 Malformed TLP 错误。RC 设备在与 EP 设备对接时,对端的 EP 设备的 MPS 可能各不相同,需要 RC 端去适配 EP 端的 mps, 如果 EP 设备接收的 TLP 数据长度超过了它的 MPS 配置,该设备就会认为该 TLP 非法。

查询

使用 lspci 查询 MPS,比如使用 lspci -s 00:02.0 -vvv | grep DevCtl: -C 2 查询 02:00.0 的设备的 MPS 1


查出来 MPS 的大小为 128 bytes


设置

设置方法参考这里 20,但有时候设置会不生效,这个也是和硬件支持的能力相关联的。


内核实现
MPS 配置类型

linux 内核中,当前有 4 种配置 MPS 的类型,定义在 include/linux/pci.h 21 中,

enum pcie_bus_config_types {
        PCIE_BUS_TUNE_OFF,      /* Don't touch MPS at all */
        PCIE_BUS_SAFE,          /* Use largest MPS boot-time devices support */
        PCIE_BUS_PERFORMANCE,   /* Use MPS and MRRS for best performance */
        PCIE_BUS_PEER2PEER,     /* Set MPS = 128 for all devices */
};
类型说明
PCIE_BUS_TUNE_OFF不对 PCIe MPS ( Max Payload Size) 进行调整,而是使用 BIOS 配置好的默认值。
PCIE_BUS_SAFE将每个设备的 MPS 都设为 Root Complex 下所有设备支持的 MPS 中的最大值。 指的是设置最小的那个设备的 MPS 为所有设备的 MPS
PCIE_BUS_PERFORMANCE将设备的 MPS 设为其上级总线允许的最大 MPS,同时将 MRRS ( Max Read Request Size ) 设为能支持的最大值 (但不能大于设备或总线所支持的 MPS 值)
PCIE_BUS_PEER2PEER所有的设备的 MPS 都设置为 128 B,以确保支持所有设备之间的点对点DMA,同时也能保证热插入 ( hot-added ) 设备能够正常工作,但代价是可能会造成性能损失。

内核在启动的 cmdline 中可以配置 pci_bus_config,如:

pci=pcie_bus_perf

内核配置 MPS

结合代码分析 linuxMPS 的配置, 以及看 MPS 是怎么生效的:

void pcie_bus_configure_settings(struct pci_bus *bus)
{
        u8 smpss = 0;

        if (!bus->self)
                return;

        if (!pci_is_pcie(bus->self))
                return;

        /*
         * FIXME - Peer to peer DMA is possible, though the endpoint would need
         * to be aware of the MPS of the destination.  To work around this,
         * simply force the MPS of the entire system to the smallest possible.
         */
        if (pcie_bus_config == PCIE_BUS_PEER2PEER)           -------  (1)
                smpss = 0;

        if (pcie_bus_config == PCIE_BUS_SAFE) {
                smpss = bus->self->pcie_mpss;

                pcie_find_smpss(bus->self, &smpss);            ------- (2)
                pci_walk_bus(bus, pcie_find_smpss, &smpss);         
        }

        pcie_bus_configure_set(bus->self, &smpss);              -------- (3)
        pci_walk_bus(bus, pcie_bus_configure_set, &smpss);
}

(1) 当 pcie_bus_configPCIE_BUS_PEER2PEER 时,设置 smpss 的值为 0, 最终通过pcie_write_mps(dev, 128 << smpss) 写到配置空间里,所以 peer2peer 的配置下,mps 被固定 128 B

(2) 当 pcie_bus_configPCIE_BUS_SAFE 时,pcie_find_smpss() 会被调用,该函数的作用是寻找一个最小的 mps,如下所示:

static int pcie_find_smpss(struct pci_dev *dev, void *data)
{
	u8 *smpss = data;
	
	if (*smpss > dev->pcie_mpss)
		*smpss = dev->pcie_mpss;

	return 0;
}

(3) pcie_bus_configure_set 会将 mps 设置写进配置空间, 会对 pci_bus_perf 做特殊处理,在pci_bus_perf 配置下: 会对每一个非根节点取本身和父节点的 mps 的较小值作为自己的 mps

同时 pci_bus_perf 配置下也会对 pcie_write_mrrs() 进行调用, 将 MRRS 设为各个节点能支持的最大值 ( 但不能大于设备或总线所支持的 MPS 值 )。

结合 Document 和代码分析,最终我们可以得出 MPS 的配置规则如下图所示:

类型说明图例颜色
PCIE_BUS_TUNE_OFF不对 PCIe MPS ( Max Payload Size) 进行调整,而是使用 BIOS 配置好的默认值。绿色
PCIE_BUS_SAFE将每个设备的 MPS 都设为 Root Complex 下所有设备支持的 MPS 中的最大值。 指的是设置最小的那个设备的 MPS 为所有设备的 MPS橙色,即所有的设备的mps设置为 256 B
PCIE_BUS_PERFORMANCE将设备的 MPS 设为其上级总线允许的最大 MPS,同时将 MRRS ( Max Read Request Size ) 设为能支持的最大值 (但不能大于设备或总线所支持的 MPS 值)黄色,除了根节点以外,比较自己设备的 MPS 和父节点的 MPS, 选择较小的那一个设为自己的 MPS,所以除了 RCMPS, 其他节点的 MPS 都设置为 256 B, 此时,还需要更新各个节点 MRRS 的大小,保证 MRRS 的值不大于自身的 MPS 的值。
PCIE_BUS_PEER2PEER所有的设备的 MPS 都设置为 128 B,以确保支持所有设备之间的点对点DMA,同时也能保证热插入 ( hot-added ) 设备能够正常工作,但代价是可能会造成性能损失。蓝色,保证DMA 的通信,所有设备的 MPS 设为 128 B

PCIe Max Read Request Size

概念

PCIe Max Read Request Size(MRRS)表示每一个读请求所能够读到的最大字节数。
PCIe 设备发送 Memory Read 请求 TLP 时,该 TLP 所请求的数据的大小不能超过 MRRS 的值。


作用

MRRSMPS 并没有直接的联系,但是有时候会有间接影响。
举个例子,一个典型的 PCIe 拓扑结构, 中间的红色数字 MPS 的值。假设发起一个 Memory Read TLP 请求操作,

根据上文的分析,这种情况下,RCEP 的通信会失败,因为他们的 MPS 的值不相等。
但是如果设置各个节点的 MRRS 的值为 128 B,那么虽然 RC 设备的 MPS256 B,但是实际能够传输的大小却受到 MRRS 的影响,变成 128 B,这种情况下,实际上传输的 TLP 报文最终是 128 B,可以满足各节点 MPS 一致的条件,可以传输成功。


MRRS 的值不能设置的太小,

  • 如果 MRRS 设置太小,会影响实际 TLPMPS
  • 如果我们设定 MRRS128 B,我们读 64 KB 数据时,就需要发送 512 ( 64KB / 128 B = 512 ) 次读请求,这样的话,就增加了很多额外的开销,对数据传输速率也是一种伤害,所以,为了提高特别是大 Block Size data 的传输效率,尽量把 MRRS 设的大一点,用更少的次数传递更多的数据。

但是 MRRS 也不能设置过大.

  • PCIe 链路中,我们不能一直发送 TLP,而是需要接受端有足够的接受空间之后,才能继续发送 TLPbuffer 空间太大的话对于 EP 的设备而言是比较苛刻的,所以在实际的设计中,也会对 MRRS 的大小进行限制。

查询

使用 lspci 查询 MRRS,比如使用 lspci -s 00:02.0 -vvv | grep maxReadReq 查询 02:00.0 的设备的 MRRS 1


查出来 MRRS 的大小为 512 B


PCIe 链路性能损耗

  1. EncodeDecode(编解码损耗)
    8 / 10 转换,或者 128 / 130 转换,即对数据重新编码,以保证链路上实际传输时 10 的总体比例相当,且不要过多连续的 10,同时把时钟信息嵌入数据流,避免高频时钟信号产生 EMI 的问题。
    对于 8 / 10 转换,会带来20%的性能损耗,因为为了传输1个 byte 数据需 10个 bit
    对于 128 / 130 转换,这部分的性能损耗可忽略不计。

  2. TLP Packet Overhead(数据包的额外开销)
    每个 Payload 在先后经过事务层、数据链路层和物理层时会加上大概20 ~ 30 bytes 的额外开销,以保证传输的一致性和完整性。

  3. Traffic Overhead(为了补偿时钟偏差而做的Skip带来的损耗)
    PCIe Spec 规定每两个 TLP 之间加入 Skip 来做时钟偏差补偿。

  4. Link Protocol Overhead(即ACK/NAK带来的开销)
    需要通过 ACK / NAK 的发送来告诉 TLP 发送端 TLP 的接收情况。这个 ACK / NAK 的发送会占用带宽。
    ACK / NAK 的发送需要一个平衡:

  • 允许接收端接收多个 TLP 后再发一个 ACK 给发送端确认,以减少 ACK 发送的数量;
  • 不能允许过多 TLP 的连续发送才发送 ACK 进行确认,因为发送端的 Replay buffer 是有限的,一旦满了则后续的 TLP 无法再发送。
  1. Flow control(流量控制带来的开销)
    UpdateFCDLLP 被用以告知发送端是否可以发 TLP,防止接收端 receive buffer 超额。这个 UpdateFC 的发送会占用带宽。
    UpdateFC 的发送需要一个平衡:
  • 允许接收端以较低的频率发送 UpdateFC 给发送端,以减少对带宽的占用;
  • 发送 UpdateFC 的频率较低意味着对接收端的 receive buffer 的要求更高。
  1. System Parameters(系统数据包通信时为了满足必要的格式带来的损耗)
    MPSMRRSRCB ( Read Completion Boundary )

PCIe 链路训练、枚举扫描、配置BAR

参考这里 22

一般来说,当系统上电后,

  1. 链路训练
    链路训练完成后进入 Gen 1 模式。但如果双方支持更高的速率,则立即进行Gen 2 / 3 / 4 速率的训练。
    当链路训练状态机再次进入 L0 状态,链路训练完成。

  2. 枚举扫描
    host 会自动查询 PCIe 设备枚举以获取总线拓扑结构。PCIe 的配置读 TLP 报文中包含响应设备的 bus_numberfunction_number 以及device_number。因此 PCIe 设备需要知道自己的 bus_number 是多少。
    当总线上存在多种 PCIe 设备时,主机 host 会依照深度优先的算法对全部的设备树上的 PCIe 设备进行枚举查询,统称 PCIe 设备枚举。
    除一些特殊系统外,普通的系统只会在开机阶段进行设备的扫描,启动成功后,即枚举过程结束后,即使插入一个 PCIe 设备,系统也不会再去识别它。
    枚举扫描过程中,CPU 会通过配置读 TLP 读取 PCIe 配置空间的的 verdor id 和头标类型寄存器以及其他配置寄存器,此时 PCIe 返回的读返回TLP报文中 complete_idbus_numberfunction_numberdevice_number )为全 0,因此此时 PCIe 还不知道自己的 BDF
    CPU 完成对 PCIe 的配置读后,会发起配置写 TLP,此时 PCIe 接受到 CPU 发来的配第一次置写 TLP,会从 TLP 中解析出 bus_number 字段存下来。随后的配置读 TLP 返回中,就会使用 bus_number、和 function_numberdevice_number 拼接成 complete_id

  3. PCIe BAR 配置
    枚举扫描完成后,会配置基地址寄存器,给 PCIe 分配地址空间。通过对 BA0/1BAR2/3 等基地址寄存器先进行写全32’hffff_ffff,得到信息确认 PCIe 想申请 32bit 地址还是 64bit 地址以及获得的地址空间,从而分配基地址。
    完成基地址配置后,就可以通过 Memory TLP 读写进行寄存器的访问了。


PCIe 枚举扫描

参考这里 23

当总线上存在多种 PCIe 设备时,主机 host 启动后是怎么找到并认出它们谁是谁的呢?
—— Host 会依照深度优先的算法对全部的设备树上的 PCIe 设备进行枚举查询,统称 PCIe 设备枚举。

其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。
一个 PCI Bridge 设备只能新开一条 PCI Bus 总线,PCI Bus 总线上又可以连接 PCI Bridge 设备,继而又新开 PCI Bus 总线。同时,PCI 扫描采用深度优先算法,因此先扫描到的 PCI Bus 总线,就优先编号。
枚举过程中 Host 通过配置读事物包来获取下游设备的信息,通过配置写事物包对下游设备进行设置。


枚举扫描过程

以下面的例子为例对 PCIe 设备枚举过程进行说明:

  1. PCI Host Bridge 扫描 Bus 0 上的设备(在一个处理器系统中,一般将 Root Complex 中与 Host Bridge 相连接的 PCI 总线命名为 PCI Bus 0 ),系统首先会忽略 Bus 0 上的 EP 等不会挂接 PCI Bridge 的设备,Bridge 1Bridge 2

    主桥发现 Bridge 1 后,将 Bridge1 下面的 PCI Bus 定为 Bus 1,系统将初始化 Bridge 1 的配置空间,并将该桥的 Primary Bus NumberSecondary Bus Number 寄存器分别设置成 01,以表明 Bridge 1 的上游总线是 0,下游总线是 1,由于还无法确定 Bridge 1 下挂载设备的具体情况,系统先暂时将 Subordinate Bus Number 设为 0xFF,如上所示。


2. 系统开始扫描 Bus 1,将会发现 Bridge 3,并发现这是一个 Switch 设备。系统将 Bridge 3 下面的 PCI Bus 定为 Bus 2,并将该桥的 Primary Bus NumberSecondary Bus Number 寄存器分别设置成 12,和上一步一样暂时把 Bridge 3Subordinate Bus Number 设为 0xFF,如上所示。


3. 系统继续扫描 Bus 2,将会发现 Bridge 4。继续扫描,系统会发现 Bridge 4 下面挂载的NVMe SSD 设备,系统将 Bridge 4 下面的 PCI Bus 定为 Bus 3,并将该桥的 Primary Bus NumberSecondary Bus Number 寄存器分别设置成 23,因为 Bus 3 下面挂的是端点设备(叶子节点),下面不会再有下游总线了,因此 Bridge 4Subordinate Bus Number 的值可以确定为 3


4. 完成 Bus 3 的扫描后,系统返回到 Bus 2 继续扫描,会发现 Bridge 5。继续扫描,系统会发现下面挂载的 NIC 设备,系统将 Bridge 5 下面的 PCI Bus 设置为 Bus 4,并将该桥的 Primary Bus NumberSecondary Bus Number 寄存器分别设置成 24,因为 NIC 同样是端点设备,Bridge 5Subordinate Bus Number 的值可以确定为 4


5. 除了 Bridge 4Bridge 5 以外,Bus 2 下面没有其他设备了,因此返回到 Bridge 3Bus 4是找到的挂载在这个 Bridge 下的最后一个 Bus 号,因此将 Bridge 3Subordinate Bus Number 设置为 4Bridge 3 的下游设备都已经扫描完毕,继续向上返回到 Bridge 1,同样将 Bridge 1Subordinate Bus Number 设置为 4


6. 系统返回到 Bus 0 继续扫描,会发现 Bridge 2,系统将 Bridge 2 下面的 PCI Bus 定为 Bus 5。并将 Bridge 2Primary Bus NumberSecondary Bus Number 寄存器分别设置成 05Graphics card 也是端点设备,因此 Bridge 2Subordinate Bus Number 的值可以确定为 5

至此,挂在 PCIe 总线上的所有设备都被扫描到,枚举过程结束,Host 通过这一过程获得了一个完整的 PCIe 设备拓扑结构。

系统上电以后,Host 会自动完成上述的设备枚举过程。除一些专有系统外,普通系统只会在开机阶段进行设备的扫描,启动成功后(枚举过程结束),即使插入一个 PCIe 设备,系统也不会再去识别它。


Linux 与 PCIe 设备树

PCIe 的树形拓扑结构为:PCI Bridge(包括 HOST 主桥)下面可以延伸出一条 PCI BusPCI 总线上面挂着 PCIe 设备和 PCI Bridge

我们可以通过 /sys/class/pci_bus/ 目录查看 linux 系统下有多少条 PCI 总线,如下所示:

[root@localhost ~]# ls /sys/class/pci_bus/
0000:00  0000:01  0000:02  0000:03  0000:04  0000:05  0000:06  0000:07  0000:08  0000:09

由上面的输出结果可得知一共有 10pci bus 总线,开头的 0000:00 就是 Host Bridge 下的第一条总线了。

我们也可以通过 /sys/bus/pci/devices/ 目录查看 linux 系统下有多少个 PCIe 设备(以及相应的设备号 BDF),如下所示:

[root@localhost ~]# ls /sys/bus/pci/devices/
0000:00:00.0  0000:00:03.0  0000:00:19.0  0000:00:1c.0  0000:00:1f.0  0000:01:00.0  0000:02:0c.0  0000:04:00.0
0000:00:01.0  0000:00:14.0  0000:00:1a.0  0000:00:1c.1  0000:00:1f.3  0000:02:04.0  0000:02:10.0  0000:09:00.0
0000:00:02.0  0000:00:16.0  0000:00:1b.0  0000:00:1d.0  0000:00:1f.5  0000:02:08.0  0000:02:14.0

通过 lspci -tv 命令,我们可以获得当前 linux 系统下的 PCIe tree,如下所示:

[root@localhost ~]# lspci -tv
-[0000:00]-+-00.0  Intel Corporation 4th Gen Core Processor DRAM Controller
           +-01.0-[01-07]----00.0-[02-07]--+-04.0-[03]--
           |                               +-08.0-[04]----00.0  Device 1ded:1020
           |                               +-0c.0-[05]--
           |                               +-10.0-[06]--
           |                               \-14.0-[07]--
           +-02.0  Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor Integrated Graphics Controller
           +-03.0  Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor HD Audio Controller
           +-14.0  Intel Corporation 8 Series/C220 Series Chipset Family USB xHCI
           +-16.0  Intel Corporation 8 Series/C220 Series Chipset Family MEI Controller #1
           +-19.0  Intel Corporation Ethernet Connection I217-LM
           +-1a.0  Intel Corporation 8 Series/C220 Series Chipset Family USB EHCI #2
           +-1b.0  Intel Corporation 8 Series/C220 Series Chipset High Definition Audio Controller
           +-1c.0-[08]--
           +-1c.1-[09]----00.0  Intel Corporation I210 Gigabit Network Connection
           +-1d.0  Intel Corporation 8 Series/C220 Series Chipset Family USB EHCI #1
           +-1f.0  Intel Corporation C220 Series Chipset Family H81 Express LPC Controller
           +-1f.3  Intel Corporation 8 Series/C220 Series Chipset Family SMBus Controller
           \-1f.5  Intel Corporation 8 Series/C220 Series Chipset Family 2-port SATA Controller 2 [IDE mode]

如上图,红色方框中的就是 Host Bridge Bus 0 上的 PCIe 设备,包括 PCIe EP 设备和 PCI Bridge 设备。

绿色方框中的 [00~03].0 ,其中 01.0 之后还有树形结构,所以 01.0 就是 PCI Bridge 设备了。
同时可以知道,PCI Bridge 下面会有新开的 PCI Bus 总线,±01.0-[01-07] 后面的 [01-07] 就是 01.0 PCI Bridge 下面挂接的 PCI Bus 总线了。
[00~03].0 中的另外 3 个设备下面没有树形结构,也没有后接 [ ] ,因此就是普通的 PCI 终端设备,即 0000:00:00.0,0000:00:01.0,0000:00:02.0,0000:00:03.0,如下图所示:

为了验证我们的结论,我们通过命令查看 0000:00:00.00000:00:01.00000:00:02.00000:00:03.0 对应的设备信息,如下所示:

[root@localhost platform] lspci -s 0000:00:00.0 -v # 0000:00:00.0 为 DRAM 控制器
00:00.0 Host bridge: Intel Corporation 4th Gen Core Processor DRAM Controller (rev 06)
        Subsystem: Intel Corporation 4th Gen Core Processor DRAM Controller
        Flags: bus master, fast devsel, latency 0
        Capabilities: [e0] Vendor Specific Information: Len=0c <?>
        Kernel driver in use: hsw_uncore

[root@localhost platform] lspci -s 0000:00:01.0 -v  # 0000:00:01.0 为 pci 桥设备
00:01.0 PCI bridge: Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor PCI Express x16 Controller (rev 06) (prog-if 00 [Normal decode])
        Flags: bus master, fast devsel, latency 0, IRQ 24
        Bus: primary=00, secondary=01, subordinate=07, sec-latency=0
        Memory behind bridge: f0000000-f41fffff
        Capabilities: [88] Subsystem: Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor PCI Express x16 Controller
        Capabilities: [80] Power Management version 3
        Capabilities: [90] MSI: Enable+ Count=1/1 Maskable- 64bit-
        Capabilities: [a0] Express Root Port (Slot+), MSI 00
        Capabilities: [100] Virtual Channel
        Capabilities: [140] Root Complex Link
        Kernel driver in use: pcieport
        Kernel modules: shpchp

[root@localhost platform] lspci -s 0000:00:02.0 -v   # 0000:00:02.0 为 VGA控制器
00:02.0 VGA compatible controller: Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor Integrated Graphics Controller (rev 06) (prog-if 00 [VGA controller])
        Subsystem: Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor Integrated Graphics Controller
        Flags: bus master, fast devsel, latency 0, IRQ 40
        Memory at f4400000 (64-bit, non-prefetchable) [size=4M]
        Memory at e0000000 (64-bit, prefetchable) [size=256M]
        I/O ports at f000 [size=64]
        Expansion ROM at <unassigned> [disabled]
        Capabilities: [90] MSI: Enable+ Count=1/1 Maskable- 64bit-
        Capabilities: [d0] Power Management version 2
        Capabilities: [a4] PCI Advanced Features
        Kernel driver in use: i915
        Kernel modules: i915

[root@localhost platform] lspci -s 0000:00:03.0 -v     # 0000:00:03.0 为音频设备
00:03.0 Audio device: Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor HD Audio Controller (rev 06)
        Subsystem: Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor HD Audio Controller
        Flags: bus master, fast devsel, latency 0, IRQ 43
        Memory at f4934000 (64-bit, non-prefetchable) [size=16K]
        Capabilities: [50] Power Management version 2
        Capabilities: [60] MSI: Enable+ Count=1/1 Maskable- 64bit-
        Capabilities: [70] Express Root Complex Integrated Endpoint, MSI 00
        Kernel driver in use: snd_hda_intel
        Kernel modules: snd_hda_intel

可以看到,0000:00:01.0 确实是 PCI Bridge 设备,其他三个为普通 PCI EP 设备。

我们可以进一步验证,1c.01c.1PCI Bridge 设备,其中 1c.0 下面挂接 Bus 08 总线, 1c.1 下面挂接 Bus 09 总线, 09 总线下面还有一个 PCI 设备:0000:09:00.0,如下所示:



为了验证我们的结论,我们通过命令查看这几个设备的信息:

[root@localhost platform] lspci  -s 0000:00:1c.0 -v # 0000:00:1c.0 为 pci 桥设备
00:1c.0 PCI bridge: Intel Corporation 8 Series/C220 Series Chipset Family PCI Express Root Port #1 (rev d5) (prog-if 00 [Normal decode])
        Flags: bus master, fast devsel, latency 0, IRQ 25
        Bus: primary=00, secondary=08, subordinate=08, sec-latency=0
        I/O behind bridge: 00002000-00002fff
        Memory behind bridge: df200000-df3fffff
        Prefetchable memory behind bridge: 00000000df400000-00000000df5fffff
        Capabilities: [40] Express Root Port (Slot+), MSI 00
        Capabilities: [80] MSI: Enable+ Count=1/1 Maskable- 64bit-
        Capabilities: [90] Subsystem: Intel Corporation 8 Series/C220 Series Chipset Family PCI Express Root Port #1
        Capabilities: [a0] Power Management version 3
        Kernel driver in use: pcieport
        Kernel modules: shpchp

[root@localhost platform] lspci  -s 0000:00:1c.1 -v # 0000:00:1c.1 为 pci 桥设备
00:1c.1 PCI bridge: Intel Corporation 8 Series/C220 Series Chipset Family PCI Express Root Port #2 (rev d5) (prog-if 00 [Normal decode])
        Flags: bus master, fast devsel, latency 0, IRQ 26
        Bus: primary=00, secondary=09, subordinate=09, sec-latency=0
        I/O behind bridge: 0000e000-0000efff
        Memory behind bridge: f4800000-f48fffff
        Capabilities: [40] Express Root Port (Slot+), MSI 00
        Capabilities: [80] MSI: Enable+ Count=1/1 Maskable- 64bit-
        Capabilities: [90] Subsystem: Intel Corporation 8 Series/C220 Series Chipset Family PCI Express Root Port #2
        Capabilities: [a0] Power Management version 3
        Kernel driver in use: pcieport
        Kernel modules: shpchp

[root@localhost platform] lspci  -s 0000:09:00.0 -v  # 0000:09:00.0 为 以太网控制器
09:00.0 Ethernet controller: Intel Corporation I210 Gigabit Network Connection (rev 03)
        Subsystem: DFI Inc Device 100a
        Flags: bus master, fast devsel, latency 0, IRQ 17
        Memory at f4800000 (32-bit, non-prefetchable) [size=512K]
        I/O ports at e000 [size=32]
        Memory at f4880000 (32-bit, non-prefetchable) [size=16K]
        Capabilities: [40] Power Management version 3
        Capabilities: [50] MSI: Enable- Count=1/1 Maskable+ 64bit+
        Capabilities: [70] MSI-X: Enable+ Count=5 Masked-
        Capabilities: [a0] Express Endpoint, MSI 00
        Capabilities: [100] Advanced Error Reporting
        Capabilities: [140] Device Serial Number 00-01-29-ff-ff-97-f7-48
        Capabilities: [1a0] Transaction Processing Hints
        Kernel driver in use: igb
        Kernel modules: igb

再次回到 PCIe tree 的结构图,如下所示:

我们可以留意到,±01.0-[01-07]----00.0-[02-07]–±04.0-[03]-- 这一长串中,

±01.0-[01-07] 代表 0000:00 总线上的 0000:00:01.0 设备挂接 [01-07] 七条总线,其中 01 总线是与 0000:00:01.0 PCI 设备相连接的总线;

[01-07]----00.0-[02-07]01 总线下的设备 0000:01:00.0 又是一个 PCI Bridge 设备,下面挂接 [02-07] 六条总线,其中 02 总线是与 0000:01:00.0 设备相连接的总线;

同理,02 总线下面挂接的都是 PCI Bridge 设备,PCI Bridge 设备再新开新的 PCI 总线。可以看到,02 总线上的设备 0000:02:08.0 挂接着 04 总线,04 总线上还挂接着 0000:04:00.0 PCI 设备。


系统上电以后,Host 会自动完成上述的设备枚举过程。除一些专有系统外,普通系统只会在开机阶段进行设备的扫描。
当我们安装一个 PCIe 设备驱动后,可以在 /sys/bus/pci/drivers 目录下找到我们安装的 PCIe 设备驱动模块,如下所示:

[root@localhost ~] ls /sys/bus/pci/drivers
agpgart-intel  ata_generic  ehci-pci    i915    lpc_ich    ohci-pci   pci-stub  snd_hda_intel     xhci_hcd
agpgart-sis    ata_piix     hsw_uncore  igb     mei_me     pata_acpi  serial    uhci_hcd
agpgart-via    e1000e       i801_smbus  ioapic  mlx5_core  pcieport   shpchp    xen-platform-pci
[root@localhost platform] insmod  nsa_dma.ko 					  # 安装驱动模块

[root@localhost platform] ls /sys/bus/pci/drivers  		 # 多出 nsa_dma 驱动模块
agpgart-intel  ata_generic  ehci-pci    i915    lpc_ich    nsa_dma    pcieport  shpchp         xen-platform-pci
agpgart-sis    ata_piix     hsw_uncore  igb     mei_me     ohci-pci   pci-stub  snd_hda_intel  xhci_hcd
agpgart-via    e1000e       i801_smbus  ioapic  mlx5_core  pata_acpi  serial    uhci_hcd
[root@localhost platform]# ls /sys/bus/pci/devices/ 	 # PCIe设备数量不变,因为只会在开机阶段进行PCIe设备扫描
0000:00:00.0  0000:00:03.0  0000:00:19.0  0000:00:1c.0  0000:00:1f.0  0000:01:00.0  0000:02:0c.0  0000:04:00.0
0000:00:01.0  0000:00:14.0  0000:00:1a.0  0000:00:1c.1  0000:00:1f.3  0000:02:04.0  0000:02:10.0  0000:09:00.0
0000:00:02.0  0000:00:16.0  0000:00:1b.0  0000:00:1d.0  0000:00:1f.5  0000:02:08.0  0000:02:14.0

对于 nvme 设备,同时显示其 BDFPCIe 设备号的方法,可参考这里 24,使用 readlink -f /sys/class/nvme/nvme*


PCIe 供电

OS 层面上,简单的 reboot 等命令属于软重启 25‘ 26,并不会对 PCIe Card 的供电进行上下电。但是可以通过以下命令来移除 PCIe 设备后再重新从 PCIe Bus 加载它以在不需要重启 PC 的前提下利用 Linux 内核对 PCIe 设备进行上下电 27’ 18。

echo "1" > /sys/bus/pci/devices/DDDD\:BB\:DD.F/remove
sleep 1
echo "1" > /sys/bus/pci/rescan

工具

lspci 其实是一个开源工具 pciutils 28 的命令,对应命令还有 setpci(用于读写 PCIe 配置寄存器)。

如果你的系统中找不到这个命令,可以执行 yum install pciutils 进行在线安装。
如果需要离线安装,工具官网或者内核官网也可以下载到源码,阅读分析这些源码对于开发 PCIe 相关内容有极大的帮助。

Windows 下,有一个很久没有更新的工具,叫 pcitree 29。

只能运行于 win7 以及之前的 windows 版本。详情参照 user guide


lspci

  • lspci -tv
    查看 PCI 设备拓扑结构

  • lspci -s [b:d: f]
    查看 PCI 设备详细信息

  • lspci -s [b:d:f] -x
    PCI 标准配置头空间 -x

  • lspci -s [b:d:f] -xxx
    PCI Capbility 配置空间 -xxx


红色框框中的第一列对应具体的偏移,第二列对应的是设备的 Capability id, 第三列对应的是 Next Capability id 的偏移。

所以 40: 0d 48 表示偏移 0x40 的位置的 Capability id0xdSSVID ); 它指向的下一个capability 位于 0x48 处,偏移 0x48 的位置的 Capability id0x1 (即 power management ); 它指向的下一个 capabity 位于 0x50 处,偏移 0x50 的位置的 Capability id0x10 ( MSI );它指向的下一个 Capability 位于 0x8c 处, 偏移 0x8c 的位置的 Capability id0x0,查找结束。

得到的结果是可以与 lspci 的结果对应的:

  • lspci -s [b:d:f] -xxxx
    PCIe 扩展配置空间 -xxxx

setpci

可以用 setpci 命令修改配置空间。语法如下,

setpci -s 00:00.0 0x地址.L=0x值       

修改设备地址的数值,一次修改 4 个字节。


示例

1.

举个例子 30, 上面有提到往 PCI 设备的 BAR 寄存器写全 1 可以计算 BAR 空间的大小需求。

  1. 通过 setpci --dumpregs 可以查看寄存器偏移,如下所示,
[root@localhost ~] setpci --dumpregs
cap pos w name
     00 W VENDOR_ID
     02 W DEVICE_ID
     04 W COMMAND
     06 W STATUS
     08 B REVISION
     09 B CLASS_PROG
     0a W CLASS_DEVICE
     0c B CACHE_LINE_SIZE
     0d B LATENCY_TIMER
     0e B HEADER_TYPE
     0f B BIST
     10 L BASE_ADDRESS_0
     14 L BASE_ADDRESS_1
     18 L BASE_ADDRESS_2
     1c L BASE_ADDRESS_3
     20 L BASE_ADDRESS_4
     24 L BASE_ADDRESS_5
	 ...

由上面的输出内容可以知道 BAR0 的偏移是 0x10

  1. 使用 lspci -s [b:d:f] -x 查看目标设备的 PCIe 配置头空间,如下所示:
[root@localhost ~] lspci -s 02:01.0 -x
02:01.0 Ethernet controller: Intel Corporation 82545EM Gigabit Ethernet Controller (Copper) (rev 01)
00: 86 80 0f 10 17 01 30 02 01 00 00 02 10 00 00 00
10: 04 00 5c fd 00 00 00 00 04 00 ff fd 00 00 00 00
20: 01 20 00 00 00 00 00 00 00 00 00 00 ad 15 50 07
30: 00 00 00 00 dc 00 00 00 00 00 00 00 07 01 ff 00

由上面的输出内容可以知道 BAR0 的值为 fd5c0004

  1. 通过 setpci 命令写 BAR0 地址全 F

    刚开始的 BAR0 的值为 fd5c0004, 写过全 F 后,由上面的输出内容可以知道 BAR0 的值为 fffe0004

  2. BAR0 的值前后变化可知最低位可写入的 bit18( 低 4bit 不算 bar 地址,是特殊标记)。如下所示,


所以 BAR0 的空间大小是 2^18 = 256K


参考链接

mark
command not found: lspci, in macOS 11.4
【转】PCIe资料总结
PCIe链路训练
深入PCI与PCIe之一:硬件篇


  1. Debugging PCIe Issues using lspci and setpci ↩︎ ↩︎ ↩︎

  2. PCIe学习笔记 ↩︎

  3. LSPCI详解分析 ↩︎ ↩︎ ↩︎

  4. Documentation/ABI/testing/sysfs-bus-pci ↩︎ ↩︎

  5. SSD学习笔记(二)PCIe ↩︎

  6. 老男孩读PCIe之六:配置和地址空间 ↩︎

  7. SSDfans ↩︎

  8. 带你了解PCIE通信原理 ↩︎ ↩︎

  9. 网卡接口绑定驱动及其使用的 bind、unbind、new_id 等 sys 文件 ↩︎ ↩︎

  10. LINUX驱动手动绑定和解绑定 ↩︎

  11. nvme用户态驱动-day01-用户态pci设备访问 ↩︎

  12. Linux驱动手动绑定和解绑定方法 ↩︎

  13. PCIE TREE ↩︎

  14. 老男孩读PCIe之七:TLP的路由 ↩︎

  15. PCIe扫盲——PCIe总线数据链路层入门 ↩︎

  16. PCIe扫盲系列博文连载目录篇(第三阶段) ↩︎

  17. PCIe扫盲——复位机制介绍(Fundamental & Hot) ↩︎

  18. How to Reset/Cycle Power to a PCIe Device? ↩︎ ↩︎

  19. PCIe扫盲——复位机制介绍(FLR) ↩︎

  20. PCIe学习笔记之Max payload size ↩︎ ↩︎

  21. linux/include/linux/pci.h ↩︎

  22. PCIE原理-004:PCIE链路训练、枚举扫描、配置BAR空间 ↩︎

  23. lspci 详解 pci 拓扑结构 与 pci 树形结构 ↩︎

  24. Map PCIe slot number (or BDF) to device number or vice versa ↩︎

  25. 硬件复位、软件复位、上电复位的异同 ↩︎

  26. 硬复位、软复位和上电复位 ↩︎

  27. sysfs-bus-pci ↩︎

  28. pciutils ↩︎

  29. pcietree ↩︎

  30. PCIe学习笔记之pcie结构和配置空间 ↩︎

本文标签: 知识点 基础 pcie