概念
PCI是一种高速的局部总线,连接周边设备,将低速的设备与高速的处理器连接起来,以满足对数据传输速率越来越高的要求。
从硬件结构上来看,PCI总线采用并行总线结构,使用点到点的连接方式;PCIe采用高速差分总线结构(串行),使用端到端的连接方式。
符合PCI总线标准的设备成为PCI设备。PCI设备分为主设备和目标设备(从设备),主设备是访问设备,目标设备是被访问设备。
PCI配置空间和内存空间是分离的。BDF(Bus Number、Device Number、Function Number)用来对所有的PCI设备进行编码,避免冲突。BDF码在BIOS进行PCI总线扫描和枚举的过程中确定,可用作查找PCI设备的索引。
PCI桥是连接PCI总线的设备,用于PCI总线扩展。桥作用管理下游的PCI总线,并转发上下游总线之间的总线事务。PCI总线系统的组成包括:HOST主桥、PCI总线、PCI设备、HOST处理器、PCI总线的负载。
HOST主桥用来隔离PCI总线域和CPU存储域,管理PCI总线域和存储域地址映射,并且完成处理器与PCI设备之间的数据交换。
PCI总线由HOST主桥或者PCI桥管理,用来连接各类PCI设备,如是声卡、网卡、显卡等。
符合PCI总线标准的设备称为PCI设备。PCI设备分为主设备和目标设备(从设备),主设备是访问设备,目标设备是被访问设备。PCI从设备只能被动地接受来自HOST主桥或者其他PCI设备的读写请求; PCI主设备可以通过总线仲裁获得PCI总线的使用权,主动地向其他PCI设备或者主存储器发出读写请求。
HOST处理器通过Host主桥发起PCI总线的配置请求总线事务,并对PCI总线上的设备和桥片进行配置。
PCI总线负载与总线频率相关,总线频率越高,能挂载的负载越少,但是整条总线能提供的带宽越大。
PCI总线树
一般的桌面系统只有一个PCI Host Bus(PCI主机总线),用于完成CPU与PCI设备之间的数据交换。一个PCI Root Bridge(PCI根桥),管理一个PCI局部总线,下挂一个PCI 总线树。PCI枚举所要访问的PCI设备是挂在在PCI总线树上的,这些设备属于同一总线空间。
PCI总线树上包含着PCI总线、PCI桥、PCI设备。 有32位和64位的,有多路地址线和数据线。典型的PCI总线树:
处理器、缓存、内存子系统通过PCI桥连接到PCI。PCI桥提供了一个低延迟路径,处理器可以通过该路径直接访问映射在内存或I/O地址空间中的任何位置的PCI设备。 PCI桥还提供了一个高带宽路径,允许PCI主机直接访问主存储器。
PCI设备的配置空间
PCI设备的基本配置空间由64字节组成,主要用来识别设备、定义主机访问PCI卡的方式。PCI设备通常将PCI配置信息存放在EEPROM中。在PCI设备上电初始化时,将EEPROM中的信息读取到PCI设备的配置空间中作为初始值,而这个过程是由硬件逻辑完成的。每个设备在出厂时,其配置空间中的值都有一些default值,枚举该设备时为每个设备分配BDS号和内存资源时,会再写入部分值。
PCI设备配置空间的代码实现(对应上图):
所有PCI兼容设备必须支持报头中的Vendor ID、Device ID、Command、Status、Revision ID、Class Code和Header Type字段。在Type 00h预定义报头中的其他寄存器的实现是可选的,这取决于设备的功能。
- Vendor ID和Device ID用来表示设备自身;通过对比这两个ID可以判断所找的设备是否为目标设备。BIOS代码在枚举设备的时候,会去读每个设备的VID,如果读到的是不是FFFF,则表示此位置有设备存在,若为FFFF,则表示设备不存在。
- Revision ID 记载着PCI设备的修订标识符(版本号),由供应商提供。
- Class code(类代码寄存器)是一个24位只读的缓存器,它被分成三个字段:基本类别(Base Class)子类别(Sub Class)及程序界面(Programming Interface),较高的字节定义功能的基本类别,中间的字节定义在基本类别里的子类别,较低的字节定义程序界面。Class code记载设备的分类,用于供系统软件识别当前PCI设备的分类(识别设备)。
- Header Type寄存器:第7位为1表示当前PCI设备时多功能设备,为0表示为单功能设备。第6~0位表示当前配置空间的类型,为0表示该设备使用PCI 设备的配置空间;为1表示使用PCI桥的配置空间;为2表示使用Cardbus桥片的配置空间。通过BAR寄存器,加上内部寄存器相对于BAR的偏移地址就可以访问PCI设备内的寄存器和其他资源。
- Latency Timer:在PCI设备中,多个设备共享一条总线带宽。该寄存器用来控制PCI设备占用PCI总线的时间。
- 基址寄存器BAR可以寻址IO地址空间和Memory地址空间。最低位是只读位,为0时表示寄存器是Memory地址译码,为1时表示寄存器是IO地址译码。通过BAR寄存器,加上内部寄存器相对于BAR的偏移地址访问PCI设备内的寄存器和其他资源。
- Cache Line Size寄存器记录HOST处理器使用的Cache行长度。在PCI总线中和Cache相关的总线事务。
- 地址0x10-0x24包含了6个基址寄存器(BAR),保存着PCI设备使用的地址空间的基地址,即设备在PCI总线域中的地址。每个PCI设备最多由6个基址空间(由BIOS分配)。
- Subsystem ID 和 Subsystem Vendor ID寄存器也是记录PCI设备的生产厂商和设备名称。
- Expansion ROM base address:有些PCI设备在处理器还没有运行操作系统之前,就需要完成基本的初始化设置。为了"预先执行"功能,PCI设备需要提供一段ROM,而处理器在初始化过程中将运行这段ROM程序,初始化这些PCI设备。这里记载了ROM程序运行的基地址。
- Capabilities Pointer寄存器:在PCI 设备中,改寄存器是可选的,但是在PCI-X 和 PCIE设备汇总必须支持这个寄存器,这里存放了Capabilities寄存器组的基地址,存放一些与PCI设备相关的扩展配置信息。
- Interrupt Line是系统软件对PCI设备进行配置时写入的,该寄存器记录当前PCI设备使用的中断向量号,设备驱动程序可以通过这个寄存器,判断当前PCI设备使用处理器系统中的哪个中断向量号。
- Interrupt Pin寄存器告诉设备(或者设备功能)使用哪个中断引脚。PCI总线提供了四个中断引脚:INTA#,INTB#,INTC#和INTD。 为1时表示使用INTA#引脚,依次类推。
UEFI对PCI/PCIe的支持
UEFI对于PCI总线的支持包括:
1) 提供分配PCI设备资源的Protocol;
2) 提供访问PCI设备的Protocol(UEFI提供了两类访问PCI/PCIe设备的Protocol:EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL和EFI_PCI_IO_PROTOCOL);
3) 提供PCI枚举器,枚举PCI总线上的设备以及分配设备所需的资源;
4) 提供各种Lib,方便驱动程序访问PCI/PCIe配置空间或者MMIO/IO空间。
MMIO(Memory mapping I/O)即内存映射I/O,它是PCI规范的一部分,I/O设备被放置在内存空间而不是I/O空间。从处理器的角度看,内存映射I/O后系统设备访问起来和内存一样。这样访问AGP/PCI-E显卡上的帧缓存,BIOS,PCI设备就可以使用读写内存一样的汇编指令完成,简化了程序设计的难度和接口的复杂性。简而言之,MMIO就是通过将外围设备映射到内存空间,便于CPU的访问。
EFI_PCI_IO_PROTOCOL
在PCI/PCIe设备驱动中,一般使用EFI_PCI_IO_PROTOCOL访问设备的内部资源。EFI_PCI_IO_PROTOCOL挂在在PCI/PCIe控制器上,运行在EFI启动环境中,对PCI/PCIe设备进行Memory空间和IO空间访问。函数接口代码清单:
EFI_PCI_IO_PROTOCOL在处理访问PCI/PCIe的Memory空间、IO空间和配置空间时,使用了两种类型。使用EFI_PCI_IO_PROTOCOL_ACCESS访问Memory空间和IO空间,而使用EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS访问配置空间。
访问Memory空间和IO空间的接口:
使用Io.Read()和Io.Write()函数访问IO空间,使用Memory.Read()和Memory.Write()函数访问Memory空间。BAR一共有6个,BarIndex取值范围为0~5。读写数据时,最终的访问地址由BarIndex所指向的BAR加上Offset共同决定,而地址的含义需要查看PCI/PCIe芯片厂商提供的手册说明。
访问配置空间的接口:
使用Pci.Read()和Pci.Write()函数访问PCI/PCIe设备的配置空间。参数Offset用于指定在配置空间内的偏移地址,如Offset = 0x10,表示BAR0寄存器。
PCI设备的基本配置空间由64字节组成(0x00~0x3F),PCI/PCIe设备扩展到0xFF。扩展的配置空间用来存放于MSI中断机制和电源管理相关的Capability结构。而PCIe设备扩展到0xFFF,用于存放PCIe设备独有的Capability。
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL为PCI根桥提供了抽象的IO功能,由PCI Host Bus Controller(PCI主总线驱动器)产生,一般由PCI/PCIe总线驱动枚举设备、获得Option ROM、分配PCI设备资源等。
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL函数接口:
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL提供了基本的访问接口,包括访问IO空间的接口Io、访问Memory空间的接口Mem和配置空间接口Pci。三种接口的参数类型都为EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS。接口代码清单:
参数Address在访问IO空间、Memory空间和配置空间时,其含义是不同的。对于配置空间而言,Address由BDF地址Register偏移决定,即总线号、设备号、功能号和Register共同给出寻址。
对IO空间而言,参数Address是指PCI设备IO空间的IO地址;对Memory空间,Address是指PCI设备Memory空间的Memory地址。IO地址和Memory地址是由BAR和偏移决定的,每个地址的作用需要参考对应的芯片说明手册。
PCIe系统
Root Complex(简称RC)是CPU和PCle总线之间的接口,可能包含几个组件(处理器接口、DRAM接口等),甚至可能包含几个芯片。
Bridge提供了与其他总线(如PCI或PCI- x,甚至是另一个PCle总线)的接口。允许旧的PCI或PCIX卡插入新系统。
Switch提供了扩展或聚合能力,并允许更多的设备连接到一个PCle端口。充当包路由器,根据地址或其他路由信息识别给定包需要走哪条路径。是一种PCIe转PCIe的桥。
Endpoint处于PCIe总线系统拓扑结构中的最末端,一般作为总线操作的发起者或者终结者。Endpoint只能接受来自上级拓扑的数据包或者向上级拓扑发送数据包。
PCIe系统框图:
PCI驱动
UEFI提供了两种主要模块支持PCI总线:PCI Host Bridge控制器驱动和PCI总线驱动。这两个模块是和特定的平台绑定的,在这种机制下,屏蔽了不同CPU的架构差异,提供了统一的Protocol接口。
PCI HOST Bridge控制器驱动根据系统实际IO空间和Memory Map,为PCI设备指定IO空间和Memory空间的范围,并且产生PCI HOST Bridge Resource Allocation协议供PCI总线驱动使用。该驱动还对HostBridge控制器下所有RootBridge设备产生Handle(句柄),句柄安装了PCIRootBridgeProtocol。PCI总线驱动利用EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL枚举系统中所有PCI设备,发现并获取PCI设备的Option ROM,并且调用PCI HOST Bridge Resource Allocation协议分配PCI设备资源。
每一个PCI HOST Bridge Controller下面可以接一个或者多个PCI root Bridge,PCI Root Bridge会产生 PCI local Bus。PCI设备驱动不会使用PCI Root Bridge I/O协议访问PCI设备,而是会使用PCI总线驱动为PCI设备产生的PCI IO Protocol来访问PCI设备的IO/MEMORY空间和配置空间。PCI Root Bridge I/O协议(Protocol)是安装在Root Bridge设备的句柄上,同时在该handle上也会有表明Root Bridge设备的Device Path协议(Protocol)。
PCI标准的特点
PCI设备具有独立的地址空间——PCI总线地址空间。PCI总线地址空间与存储器地址空间通过Host Bridge 隔离,CPU访问PCI设备时,必须通过Host Bridge进行地址转换;PCI设备访问主存储器时,同样需要Host Bridge进行地址转换。Host Bridge使得PCI设备和处理器可以方便地共享主存储器资源。在Host Bridge 中有许多缓存,使得CPU总线与PCI总线可以工作在各自的时钟频率上。
PCI和PCIe的差异
1、 PCI是并行总线 ,而PCIe是串行的
PCI在一个时钟周期内有32bit的数据被同时传输。地址和数据在一个时钟周期内按照协议分别一次被传输。PCI引脚定义图:
PCI接口最少需要47个引脚用于目标设备,49个引脚用于主设备,用来处理数据和寻址、接口控制、仲裁和系统功能。必须的引脚有(图左边的引脚): Address and Data、 Interface Control、 Error Reporting、 Arbitration(仲裁 master only)、 System(时钟、RST)。
2、 PCI采用总线结构,PCIe采用点对点的拓扑结构
PCI总线模型: 在传统的PCI总线模型中,一个设备通过在Bus上判断DEVSEL(设备选择,判断一个设备是否被选中)来认领一个请求。如果在一段时钟周期后没有设备认领一个请求,这个请求就被放弃。
PCIE点对点模型:PCI是一种点对点的传输模型,不像PCI总线那样,在总线上有平等认领请求的机制。所有的传送总是由一个Device发给Link上的另外一个Device。所以,对于所有接收方来说,接收方将会直接判断这个请请求是否要被认领。
3、PCIe支持4K字节的配置空间,而PCI只支持256字节的配置空间。
访问PCI/PCIe设备的过程
- 扫描整个系统空间,通过BDF获取PCI/PCIe设备的配置空间;
- 读取PCI/PCIe设备配置空间中的Vendor ID 和Device ID,确定是否为需要访问的设备;
- 找到PCI/PCIe设备后,获取其配置空间中的BAR,参照芯片手册,访问设备内部的寄存器和资源。
整个PCI枚举过程结束后,一个完整的资源分配的树建立完成。PCI设备的扫描是基于深度优先搜索算法的,下级分支最多的PCI桥将最先完成其子设备的扫描。
【本文主要参考UEFI Spec,部分内容来源作者罗冰的《UEFI编程实践》,编写粗糙,如有错误敬请谅解并欢迎指正】