PCIE学习笔记(一)PCI
PCI是 Peripheral Component Interconnect (外设部件互连标准)的缩写,它曾经是个人电脑中使用最为广泛的接口,几乎所有的主板产品上都带有这种插槽。目前该总线已经逐渐被PCI Express总线所取代。
PCI 总线
PCI 总线是一种树型结构,并且独立于 CPU 总线,可以和 CPU 总线并行操作。PCI总线上可以挂接 PCI 设备和 PCI 桥, PCI 总线上只允许有一个 PCI 主设备(同一时刻),其他的均为 PCI 从设备,而且读写操作只能在主从设备之间进行,从设备之间的数据交换需要通过主设备中转。
PCI 配置空间
PCI 配置空间为256字节的固定空间,负责设备的信息提供和管理等。头部最开始的 deviceID 和 vendorID 寄存器由 pcisig 分配,只读,vendorID 代表 pci 设备的厂商,deviceID 代表厂商的具体设备。
Header Type
0xE
偏移处是设备的 header type,决定了 PCI 配置头的布局和设备的类型。头类型有三种,在 PCIE 中只保留了前两种。
Type 0设备代表 PCI Endpoint,其配置空间如下:
每个字段的详细含义可以参照 PCI - OSDev Wiki
Base Address Register - BAR寄存器
除了配置空间以外,PCI 还有空间去实现所需的功能,如硬件存储等,这就需要额外的可访问地址。BAR 寄存器存储了设备内部空间映射到对应地址空间的基址,它并不是一个单纯的地址,它拥有自己的结构。
其中:
-
最低位Bit 0:是一个标志位,用于描述地址空间的类型,0表示内存地址空间,1表示 IO 地址空间
-
Memory Space中的Bit [2:1] - Type:用于描述内存空间的类型,00表示32位地址空间,10表示64位地址空间
-
Memory Space中的Bit 3 - Prefetchable:用于描述内存空间是否支持预取,0表示不支持,1表示支持。如果一段内存空间支持预取,它意味着读取时不会产生任何副作用,所以CPU可以随时将其预取到DRAM中。而如果预取被启用,在读取数据时,内存控制器也会先去DRAM查看是否有缓存。当然,这是一把双刃剑,如果数据本身不支持预取,那么除了可能导致数据不一致,多一次DRAM的查询还会导致速度下降。
为得到所需映射空间的大小,在 BIOS 对 BAR 进行初始化时,对 BAR 寄存器写入全1,出去最低几位的 flag,对齐位会恢复为全0,高位能写入的位为全1,就可以求出所需的大小。比如0xFFFFF000,取反之后就是0x00000FFF,加1之后就是0x00001000,也就是4KB。另外,如何这个空间不可用,那么返回全0。BIOS接着进行真正的地址分配和映射,并将这个新的地址重新写入BAR。
对于BAR空间中保存的所有的地址,我们都可以通过lspci来查看到:
1 | sudo lspci -s `BUS_NUM`:00.0 -nn -vv |
地址空间
PCI 定义了三个地址空间,分别为 Memory Address Space, I/O Address Space, Configuration Address Space,即内存地址空间,IO 地址空间和配置地址空间,其中第三个应和配置空间区别开。
BAR 中储存的地址可以是内存地址空间中的,也可以是 IO 地址空间的,其访问就是和 x86 系统中一样的访问方式。配置地址空间的访问需要发送事务层的指令(Configuration Commands)。
BDF - Bus Number, Device Number, Function Number
PCI 上所有的设备,无论是 Type 0还是 Type 1,在系统启动的时候,都会被分配一个唯一的地址,它有三个部分组成:
- Bus Number:8 bits,也就是最多256条总线
- Device Number:5 bits,也就是最多32个设备
- Function Number:3 bits,也就是最多8个功能
这就是我们常说的BDF,它类似于网络中的IP地址,一般写作 BB:DD.F
的格式。在 Linux 上,我们可以通过 lspci 命令来查看每个设备的 BDF 。比如,下面这个FCH SMBus Controller就是00:14.0:
1 | $ lspci -t -v |
知道了任何一个设备的BDF之后,我们就可以通过lspci -s `BDF` -vv
查看到这个设备的详细信息。
可以算出 PCI 配置地址空间的总大小为
Linux 中的 PCI 子系统
参考文献
PCIe(一) —— 基础概念与设备树 | Soul Orbit (r12f.com)