UEFI——PCI/PCIe

概念

        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设备的过程

  1. 扫描整个系统空间,通过BDF获取PCI/PCIe设备的配置空间;
  2. 读取PCI/PCIe设备配置空间中的Vendor ID 和Device ID,确定是否为需要访问的设备;
  3. 找到PCI/PCIe设备后,获取其配置空间中的BAR,参照芯片手册,访问设备内部的寄存器和资源。

        整个PCI枚举过程结束后,一个完整的资源分配的树建立完成。PCI设备的扫描是基于深度优先搜索算法的,下级分支最多的PCI桥将最先完成其子设备的扫描。

【本文主要参考UEFI Spec,部分内容来源作者罗冰的《UEFI编程实践》,编写粗糙,如有错误敬请谅解并欢迎指正】

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,我们需要定义一个EFI应用程序的入口函数和一个INF文件。在EDK2中,我们可以使用C语言来编写UEFI程序,而INF文件则是一个配置文件,用于指定程序的构建选项和依赖关系。 下面是一个简单的UEFI程序的入口函数,它将打开一个文件并将所有的PCI/PCIe设备信息写入该文件: ```c #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/UefiBootServicesTableLib.h> #include <Protocol/PciIo.h> EFI_STATUS EFIAPI UefiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; EFI_PCI_IO_PROTOCOL *PciIo; UINTN Segment; UINTN Bus; UINTN Device; UINTN Function; UINTN Index; CHAR16 FileName[] = L"\\EFI\\pci_info.txt"; EFI_FILE_PROTOCOL *File; CHAR8 Buffer[256]; // Open the file for writing Status = gBS->OpenProtocol(ImageHandle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)&FileSystem, ImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(Status)) { Print(L"Failed to open the file system protocol: %r\n", Status); return Status; } Status = FileSystem->Open(FileSystem, &File, FileName, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, 0); if (EFI_ERROR(Status)) { Print(L"Failed to open the file %s: %r\n", FileName, Status); return Status; } // Traverse all PCI/PCIe devices for (Segment = 0; Segment <= 0xFFFF; Segment++) { for (Bus = 0; Bus <= 0xFF; Bus++) { for (Device = 0; Device <= 0x1F; Device++) { for (Function = 0; Function <= 0x7; Function++) { // Check if the device exists Status = gBS->LocateProtocol(&gEfiPciIoProtocolGuid, NULL, (VOID **)&PciIo); if (EFI_ERROR(Status)) { continue; } // Read the device's PCI configuration space Status = PciIo->Pci.Read(PciIo, EfiPciIoWidthUint32, 0, sizeof(Buffer), &Buffer); if (EFI_ERROR(Status)) { continue; } // Write the device information to the file UnicodeSPrint(Buffer, sizeof(Buffer), L"%04x:%02x:%02x.%x\n", Segment, Bus, Device, Function); Status = File->Write(File, &sizeof(Buffer), Buffer); if (EFI_ERROR(Status)) { Print(L"Failed to write device information to the file: %r\n", Status); return Status; } } } } } // Close the file Status = File->Close(File); if (EFI_ERROR(Status)) { Print(L"Failed to close the file: %r\n", Status); return Status; } return EFI_SUCCESS; } ``` 接下来,我们需要创建一个INF文件,以指定构建选项和依赖关系。下面是一个示例INF文件: ```inf [Defines] INF_VERSION = 0x00010006 BASE_NAME = pci_info FILE_GUID = 12345678-1234-1234-1234-1234567890AB MODULE_TYPE = UEFI_APPLICATION VERSION_STRING = 1.0 ENTRY_POINT = UefiMain [Sources] pci_info.c [Packages] MdePkg/MdePkg.dec [LibraryClasses] UefiApplicationEntryPoint UefiLib UefiBootServicesTableLib PciIo [Protocols] PciIo [BuildOptions] -D NDEBUG ``` 在这个INF文件中,我们定义了以下内容: - `BASE_NAME`: 程序的基本名称,用于构建输出文件名。 - `FILE_GUID`: 程序的GUID,用于唯一标识程序。 - `MODULE_TYPE`: 程序的类型,这里是UEFI应用程序。 - `VERSION_STRING`: 程序的版本号。 - `ENTRY_POINT`: 程序的入口函数。 - `Sources`: 程序的源代码文件。 - `Packages`: 指定依赖的EDK2软件包。 - `LibraryClasses`: 指定需要链接的库文件。 - `Protocols`: 指定需要使用的协议。 - `BuildOptions`: 指定编译选项。 现在,我们可以使用EDK2的构建工具来构建程序: ``` build -p pci_info.inf -a X64 -b RELEASE ``` 这将生成一个名为pci_info.efi的可执行文件,它可以在UEFI固件中运行,并将所有PCI/PCIe设备信息写入一个名为pci_info.txt的文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值