如何在UEFI环境下使用 UEFI规范提供的接口

描述

 

目录

7.1 访问 PCI/PCIE 设备

7.1.1 与 PCI/PCIE 设备通信的机制

7.1.2 支持访问 PCI/PCIE 设备的 Protocol

7.1.3 访问 PCI/PCIE 设备示例

进行项目开发、构建产品框架的时候,最开始需要考虑的就是采用哪种通信方式让软件可以访问外部设备(简称外设)。计算机经过多年的发展,提供了非常丰富的通信协议供程序员选择。从古老的串口协议,到使用广泛的 PCI/PCIE 协议,再到现在无处不在的 USB 协议等,可供选择的方式实在太多。

在 Legacy BIOS 下,第三方开发者编写访问外设的代码是件非常辛苦的事情。主要原因在于,Legacy BIOS 对很多协议支持得并不好。以笔者常用的 SMBus 协议为例,直到现在,也没有 BIOS 厂家提供标准的中断接口。大多数时候,只能通过阅读主板芯片组规格书,了解与 SMBus 协议相关的寄存器信息,再根据标准的 SMBus 总线读写协议编写代码。市场上主板芯片组太多,这种方法写出来的代码,兼容性、稳定性都不理想,并且工作量非常大。

UEFI BIOS 的出现,解决了上述这些问题。从 UEFI 标准和 EDK2 的源码也可以看出,常用的总线协议如 PCI/PCIE、SMBus、串口等,UEFI 都已经提供了支持。对于依赖于 BIOS 接口进行产品开发的厂商来说,这是一个非常好的消息,产品的开发速度将大幅提高,稳定性和兼容性也能得到保障。

本章将介绍如何在 UEFI 环境下使用 UEFI 规范提供的接口(即各类 Protocol),通过 PCI/PCIE、SMBus 和串口访问外设。

7.1 访问 PCI/PCIE 设备

PCI(Peripheral Component Interconnect)是一种高速的局部总线。其主要目的是连接周边设备,将低速的设备与高速的处理器结合起来,以解决用户对数据传输速率越来越高的要求。PCIE 总线是在 PCI 总线上继承发展来的,其将信号传输方式从并行改为了串行, 传输速率也突飞猛进,PCI 的理论带宽为 133MB/s,而 PCIE4.0 x16 的带宽达到了 64GB/s。

从硬件结构角度看,PCI 和 PCIE 有很大的不同。PCI 总线采用并行总线结构,而 PCIE 总线使用了高速差分总线结构,使用端到端的连接方式,这使得两者采用的拓扑结构差异较大。随着技术的发展,目前市场上 PCI 设备越来越少,很多主板现在只提供 PCIE 的接口了。

这些差异对在 UEFI 下进行编程影响不大。UEFI 系统已经屏蔽了这些差异,提供了一致的访问接口,下面详细介绍如何访问 PCI/PCIE 设备。

7.1.1 与 PCI/PCIE 设备通信的机制

PCI 协议和 PCIE 协议经过多年的发展,其内容已经非常庞大。本书主要论述的是如何在 UEFI 下进行编程。站在软件工程师的角度,在访问 PCI/PCIE 设备时,实际上只要回答以下两个问题就可以了。

Ø如何在系统中找到需要访问的设备?

Ø找到设备后,如何访问设备内的寄存器或其他资源?

UEFI 规范中,抽象了 PCI 的系统架构,典型的桌面系统的 PCI 架构如图 7-1 所示。

代码

图 7-1 单 PCI Root Bridge 的桌面系统

一般的桌面系统只有一个 PCI Host Bus(PCI 主机总线),用于完成 CPU 与 PCI 设备之间的数据交换。PCI Root Bridge(PCI 根桥)一般也只有一个,它管理一个局部总线,下挂一棵 PCI 总线树。我们所要访问的 PCI 设备,就挂在这棵总线树上,它们属于同一总线空间,如图 7-2 所示。

从图 7-2 中可以看出,PCI 总线树上包含 PCI 总线、PCI 桥和 PCI 设备。系统通过三段编码的方式进行编码,即通过 Bus Number(总线号)、Device Number(设备号)和 Function Number(功能号)来编码,这种编码一般简称为 BDF 码。BDF 码在 BIOS 进行 PCI 总线扫描和枚举过程中确定,可以用来作为查找 PCI 设备的索引。

代码

图 7-2    PCI 总线树

找到 PCI 设备后,如何确定此设备就是自己要找的设备呢?每个 PCI 设备,除了主总线桥外,都会实现配置空间(主总线桥可以有选择地实现),而在配置空间中,包含了设备厂商用来标志自身的 Vendor ID(供应商 ID)和 Device ID(设备 ID)。通过比对 PCI 设备的供应商 ID 和设备 ID,可以确定所找的设备是否为目标设备。

以 X86 平台为例,可通过 CONFIG_ADDR 寄存器(0xCF8)和 CONFIG_DATA 寄存器(0xCFC),以 BDF 码的形式访问 PCI 设备,以得到设备的配置空间。图 7-3 所示为 PCI设备的基本配置空间。

代码

图 7-3    PCI 设备的配置空间

PCI 设备的基本配置空间由 64 字节组成,地址范围为 0x00~0x3F,主要用来识别设备、定义主机访问 PCI 卡的方式。从图 7-3 中可以看出,最开始的两个寄存器就是 Vendor ID 和 Device ID 寄存器,这是用来标志设备自身的寄存器,由 PCISIG 协会分配。比如Intel 集成显卡 HD620,其 Vendor ID 为 0x8086,而 Device ID 为 0x5917。

在配置空间中,从地址 0x10 至 0x24,包含了 6 个 Base Address Registe(r  基址寄存器)。这组寄存器被称为 BAR,保存了 PCI 设备使用的地址空间的基地址,也即该设备在 PCI 总线域中的地址。每个 PCI 设备最多可以有 6 个基址空间,但多数设备不会使用这么多,笔者以前常用的南京沁恒的 CH366 芯片,只使用了第一个 BAR。

BAR 可寻址 IO 地址空间或者 Memory 地址空间,其最低位是只读位,显示了可以访问哪种地址空间。值为 0 表示寄存器是 Memory 地址译码,值为 1 表示寄存器是 IO 地址译码,如图 7-4 所示。

代码

图 7-4    基地址寄存器的位分配

那么如何访问 PCI 设备内的寄存器和其他资源?答案是通过 BAR 寄存器,加上内部寄存器相对于 BAR 的偏移地址。芯片手册中,会提供关于内部资源的使用说明,以 CH366 的芯片为例,其内部寄存器说明如图 7-5 所示。

代码

图 7-5 CH366 寄存器说明(节选自《CH366 中文手册》)

CH366 的第一个 BAR 可以使用,它是 IO 地址译码的,其他 BAR 在芯片中是无效的。图 7-4 表明,第一个 BAR 加上偏移地址,就可以成为芯片内部相应功能的寄存器。至于这些内部寄存器有什么作用,则需要详细了解芯片手册才能知道。

代码

总结来说,访问 PCI/PCIE 设备的过程如下。

1)扫描整个系统空间,通过 BDF 获取 PCI/PCIE 设备的配置空间。同一总线域上(即存在一个主桥),PCIE 一共支持 256 个总线、32 个设备、8 个功能,也就是说总线号最大值为 255、设备号最大值为 31、功能号最大为 7。

2)读取 PCI/PCIE 设备配置空间中的 Vendor ID 和 Device ID,确定是否为需要访问的设备。

3)找到 PCI/PCIE 设备后,获取其配置空间中的 BAR,参照芯片手册,访问设备的内部寄存器和资源。

UEFI 中提供了两个主要的模块来支持 PCI 总线,一是 PCI Host Bridge(PCI 主桥)控制器驱动,另一个是 PCI 总线驱动。这两个模块是和特定的平台硬件绑定的,在这种机制下,屏蔽了不同的 CPU 架构差异,为软件开发者提供了比较一致的 Protocol 接口。下一节详细介绍访问 PCI/PCIE 设备的 Protocol。

7.1.2 支持访问 PCI/PCIE 设备的 Protocol

UEFI 标准中提供了两类访问 PCI/PCIE 设备的 Protocol—EFI_PCI_ROOT_BRIDGE_ IO_PROTOCOL 和 EFI_PCI_IO_PROTOCOL。前者为 PCI 根桥提供了抽象的 IO 功能,它由 PCI Host Bus Controller(PCI 主总线驱动器)产生,一般由 PCI/PCIE 总线驱动用来枚举设备、获得 Option ROM、分配 PCI 设备资源等;后者由 PCI/PCIE 总线驱动为 PCI/PCIE 设备产生,一般由 PCI/PCIE 设备驱动用来访问 PCI/PCIE 设备的 IO 空间、Memory 空间和配置空间。

这两种 Protocol 的使用方法如下。

1.使用 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL

EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 中提供了基本的访问接口,包括访问 IO 空间、Memory 空间和配置空间的接口。该 Protocol 主要由 PCI/PCIE 总线驱动使用,当然, UEFI 应用也可以使用它来遍历 PCI/PCIE 设备。

该 Protocol 中还提供了 DMA 接口,以支持总线驱动访问系统内存。代码清单 7-1 给出了 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 的函数接口。

代码清单 7-1 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 函数接口

 

typedef struct _EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL { EFI_HANDLE ParentHandle; //Protocol的父句柄EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_POLL_IO_MEM PollMem; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_POLL_IO_MEM PollIo;EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS Mem; //读写Memory空间EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS Io; //读写IO空间EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS Pci; //读写配置空间EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_COPY_MEM CopyMem; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_MAP Map; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_UNMAP Unmap;EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ALLOCATE_BUFFER AllocateBuffer; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_FREE_BUFFER FreeBuffer; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_FLUSH Flush; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_GET_ATTRIBUTES GetAttributes; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_SET_ATTRIBUTES SetAttributes; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_CONFIGURATION Configuration;UINT32 SegmentNumber;} EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL;

 

从代码清单 7-1 中可以看出,此 Protocol 提供了非常丰富的访问接口。由于篇幅所限, 无法将所有接口都介绍清楚,这里主要介绍如何读写 PCI/PCIE 设备的 3 种空间。

从 7.1.1 节的介绍中我们知道,PCI/PCIE 设备能访问的空间包括 Memory 空间、IO 空间和配置空间。对于这 3 种空间,每个 PCI/PCIE 设备必须实现配置空间,而 Memory 空间和 IO 空间的功能,则不一定实现。7.1.1 节介绍的 PCIE 芯片 CH366 就只支持 IO 空间的访问。

EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 提供了访问 Memory 空间的接口 Mem、访问 IO 空间的接口 Io 和访问配置空间的接口 Pci。这 3 个接口的参数类型都是一样的,均为EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS,详见代码清单 7-2。

代码清单 7-2 访问 IO 空间、Memory 空间和配置空间的接口

 

typedef struct {EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_IO_MEM Read;  //读数据EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_IO_MEM Write;  //写数据} EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS;typedef EFI_STATUS (EFIAPI *EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_IO_MEM) (IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *This,  //实例IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH Width,  //读写宽度IN UINT64 Address, //IO空间/Memory空间/配置空间的地址IN UINTN Count,  //读写的数据个数,单位为读写宽度WidthIN OUT VOID *Buffer //对读操作,这是目的缓冲区;对写操作,这是要写的数据缓冲区);

 

访问 3 种空间的接口都包含读数据和写数据两种操作,并且使用了同样的数据结构EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_IO_MEM。在此结构中,Width 是指读写宽度, 其值由枚举类型 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH 给出,一般包括 8 位、16 位、32 位和 64 位几种。

需要注意的是,参数 Address 在访问 IO 空间、Memory 空间和配置空间时,其含义是不同的。对配置空间而言,Address 由 BDF 地址和 Register 偏移决定,即总线号、设备号、功能号和 Register 共同给出的寻址用索引。这是一个 64 位长的参数,一般使用宏 EFI_ PCI_ADDRESS 来组合 BDF 和 Register 偏移。在 EDK2 中,这个宏定义于头文件 MdePkg IncludeProtocolPciRootBridgeIo.h 中,其内容如下。

 

#define EFI_PCI_ADDRESS(bus, dev, func, reg) (UINT64) ( (((UINTN) bus) << 24) |  (((UINTN) dev) << 16) |  (((UINTN) func) << 8) | (((UINTN) (reg)) < 256 ? ((UINTN) (reg)): (UINT64) (LShiftU64 ((UINT64) (reg), 32))))

 

对 IO 空间而言,参数 Address 是指 PCI 设备 IO 空间的 IO 地址;对 Memory 空间而言,参数 Address 是指 PCI 设备 Memory 空间的 Memory 地址。参考 4.2.1 节可知,IO 地址和 Memory 地址是由 BAR 和偏移决定的,每个地址的作用还需要查看对应芯片的说明手册。

2.使用 EFI_PCI_IO_PROTOCOL

在 PCI/PCIE 设备驱动中,一般使用 EFI_PCI_IO_PROTOCOL 来访问设备的内部资源, Protocol 挂载在 PCI/PCIE 控制器上,运行在 EFI 启动服务环境中,对 PCI/PCIE 设备进行Memory 空间和 IO 空间访问。其函数接口如代码清单 7-3 所示。

代码清单 7-3 EFI_PCI_IO_PROTOCOL 函数接口

 

typedef struct _EFI_PCI_IO_PROTOCOL { EFI_PCI_IO_PROTOCOL_POLL_IO_MEM PollMem; EFI_PCI_IO_PROTOCOL_POLL_IO_MEM PollIo;EFI_PCI_IO_PROTOCOL_ACCESS Mem; //读写Memory空间EFI_PCI_IO_PROTOCOL_ACCESS Io; //读写IO空间EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Pci; //读写配置空间EFI_PCI_IO_PROTOCOL_COPY_MEM CopyMem; EFI_PCI_IO_PROTOCOL_MAP Map; EFI_PCI_IO_PROTOCOL_UNMAP Unmap;EFI_PCI_IO_PROTOCOL_ALLOCATE_BUFFER AllocateBuffer; EFI_PCI_IO_PROTOCOL_FREE_BUFFER FreeBuffer; EFI_PCI_IO_PROTOCOL_FLUSH Flush;EFI_PCI_IO_PROTOCOL_GET_LOCATION GetLocation; EFI_PCI_IO_PROTOCOL_ATTRIBUTES Attributes; EFI_PCI_IO_PROTOCOL_GET_BAR_ATTRIBUTES GetBarAttributes; EFI_PCI_IO_PROTOCOL_SET_BAR_ATTRIBUTES SetBarAttributes; UINT64 RomSize;VOID *RomImage;} EFI_PCI_IO_PROTOCOL;
与EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 不 同 ,EFI_PCI_IO_PROTOCOL 在 处理访问 PCI/PCIE 的 Memory 空间、IO 空间和配置空间时,使用了两种类型来区分。其中, 访问 Memory 空间和 IO 空间使用的类型是 EFI_PCI_IO_PROTOCOL_ACCESS,如代码清单 7-4 所示。  

 

代码清单 7-4 访问 IO 空间和 Memory 空间的接口

 

typedef struct {EFI_PCI_IO_PROTOCOL_IO_MEM Read;  //读数据EFI_PCI_IO_PROTOCOL_IO_MEM Write; //写数据} EFI_PCI_IO_PROTOCOL_ACCESS;typedef EFI_STATUS (EFIAPI *EFI_PCI_IO_PROTOCOL_IO_MEM) (IN EFI_PCI_IO_PROTOCOL *This,  //EFI_PCI_IO_PROTOCOL实例IN EFI_PCI_IO_PROTOCOL_WIDTH Width, //读写宽度,8位、16位、32位、64位IN UINT8 BarIndex, //在配置空间中的BAR索引值IN UINT64 Offset,  //偏移寄存器,用来进行IO空间/Memory空间读写IN UINTN Count,  //读写的数据个数,单位为读写宽度WidthIN OUT VOID *Buffer //对读操作,这是目的缓冲区;对写操作,这是要写的数据缓冲区);

 

上述代码中的参数 This 指向的是与 PCI/PCIE 设备本身相关的 EFI_PCI_IO_PROTOCOL实例,因此,在访问设备时比较直接,不需要通过 BDF 等方式给出设备的地址。

IO 空间读写使用的函数为 Io.Read() 和 Io.Write() ;Memory 空间读写使用的函数为Mem.Read() 和 Mem.Write()。读写数据的时候,所需要访问的地址由 BarIndex 和 Offset 共同规定。图 7-3 所示为 PCI/PCIE 设备的配置空间,从图中可知,BAR 总共有6 个, BarIndex 值的范围为 0 至 5。最终访问的地址,等于 BarIndex 所指向的 BAR 加上 Offset。至于此地址的含义,仍旧得查看 PCI/PCIE 芯片厂家提供的说明手册。

访问配置空间使用的数据结构为 EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS,其接口说明如代码清单 7-5 所示。

代码清单 7-5 访问配置空间的接口

 

typedef struct {EFI_PCI_IO_PROTOCOL_CONFIG Read; //读数据EFI_PCI_IO_PROTOCOL_CONFIG Write; //写数据} EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS;typedef EFI_STATUS (EFIAPI *EFI_PCI_IO_PROTOCOL_CONFIG) (IN EFI_PCI_IO_PROTOCOL *This,  //EFI_PCI_IO_PROTOCOL实例IN EFI_PCI_IO_PROTOCOL_WIDTH Width, //读写宽度,8位、16位、32位、64位IN UINT32 Offset,  //偏移,在配置空间内的偏移地址IN UINTN Count,  //读写的个数,以Width为单位IN OUT VOID *Buffer //对读操作,这是目的缓冲区;对写操作,这是要写的数据缓冲区);

 

Pci.Read() 和 Pci.Write() 函数用来访问 PCI/PCIE 设备的配置空间。参数 Offset 用来指定在配置空间内的偏移地址,比如 Offset=0x10 时,是指 BAR0 寄存器。

PCI 设备的基本配置空间是由 64 字节(0x00~0x3F)组成的,这是所有 PCI/PCIE 设备必须支持的。此外,PCI/PCIE 设备还扩展了 0x40~0xFF 这段配置空间,主要用来存放于MSI 中断机制和电源管理相关的 Capability 结构。另外,PCIE 设备还支持 0x100~0xFFF 这段配置空间,这段配置空间用于存放 PCIE 设备独有的 Capability 结构。

这些配置空间的信息,都可以通过 EFI_PCI_IO_PROTOCOL 获取。至于配置空间内寄存器的具体含义,读者可以参考 PCI 标准和 PCIE 标准进行深入学习。

7.1.3 访问 PCI/PCIE 设备示例

本节准备了相应的示例,演示如何使用 7.1.2 节介绍的两种 Protocol 来遍历系统内的PCI/PCIE 设备。大多数的机器上,只存在一个 PCI 总线域(PCI  Segment),即一个主桥。因此,在使用 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 的时候,应该只会找到一个实例。我们设计的程序,其主要功能如下。

使用 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL,通过 BDF,遍历所有 PCI/PCIE设备,打印出设备的相关信息。

寻找所有的 EFI_PCI_IO_PROTOCOL 实例,直接访问每个实例的配置空间,将其信息打印出来。

本节提供的示例程序位于随书代码的 RobinPkgApplicationsListPCIMsg 目录下。示例

7-1 演示了如何获取两类 Protocol 的实例。

【示例 7-1】获取 Protocol 的实例。

 

EFI_STATUS LocatePCIRootBridgeIO(void){EFI_STATUS  Status;EFI_HANDLE  *PciHandleBuffer = NULL;UINTN  HandleIndex = 0;UINTN  HandleCount = 0;//获取PciRootBridgeIOProtocol的所有句柄Status = gBS->LocateHandleBuffer(ByProtocol, &gEfiPciRootBridgeIoProtocolGuid, NULL,&HandleCount, &PciHandleBuffer);if (EFI_ERROR(Status))  return Status;Print(L"Find PCI Root Bridge I/O Protocol: %d
",HandleCount);//获取PciRootBridgeIOProtocol实例for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++){Status = gBS->HandleProtocol( PciHandleBuffer[HandleIndex], &gEfiPciRootBridgeIoProtocolGuid, (VOID**)&gPCIRootBridgeIO);if (EFI_ERROR(Status))  continue; elsereturn EFI_SUCCESS;}return Status;}EFI_STATUS LocatePCIIO(void){EFI_STATUS  Status;EFI_HANDLE  *PciHandleBuffer = NULL;UINTN  HandleIndex = 0;UINTN  HandleCount = 0;//获取PciIoProtocol的所有句柄 Status = gBS->LocateHandleBuffer(ByProtocol, &gEfiPciIoProtocolGuid, NULL,&HandleCount, &PciHandleBuffer);if (EFI_ERROR(Status))  return Status;  //unsupport gPCIIO_Count = HandleCount;Print(L"Find PCI I/O Protocol: %d
",HandleCount);//获取PciIoProtocol实例,并存储在全局变量gPCIIOArray中for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++){Status = gBS->HandleProtocol( PciHandleBuffer[HandleIndex], &gEfiPciIoProtocolGuid, (VOID**)&(gPCIIOArray[HandleIndex]));}return Status;}

 

示例 7-1 中提供了两个函数—LocatePCIRootBridgeIO() 和 LocatePCIIO(),用来获取需要测试的两类 Protocol 的实例。获取实例的方法在 3.5 节中已经介绍过了,本节的例程用了同样的方法。EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 的实例,在大部分办公用的个人电脑中只存在一个,因此直接用全局指针变量 gPCIRootBridgeIO 存储;而 EFI_PCI_IO_ PROTOCOL 的实例存在多个,一般有多少个 PCI/PCIE 设备,就存在多少个实例,因此使用全局指针数组 gPCIIOArray[256] 来存储这些实例。

为遍历全部的 PCI/PCIE 设备,可以使用 gPCIRootBridgeIO 和 BDF 码,循环查找挂载总线上的设备,代码如示例 7-2 所示。

【示例 7-2】使用 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 遍历 PCI/PCIE 设备。

 

EFI_STATUS ListPCIMessage1(void){EFI_STATUS Status=EFI_SUCCESS; PCI_TYPE00 Pci;UINT16 i,j,k,count=0; for(k=0;k<=PCI_MAX_BUS;k++)for(i=0;i<=PCI_MAX_DEVICE;i++) for(j=0;j<=PCI_MAX_FUNC;j++){//判断设备是否存在Status = PciDevicePresent(gPCIRootBridgeIO,&Pci, (UINT8)k,(UINT8)i,(UINT8)j);if (Status == EFI_SUCCESS)  //找到了设备{++count;Print(L"%02d. Bus-%02x Dev-%02x Func-%02x: ", count,(UINT8)k,(UINT8)i,(UINT8)j);Print(L"VendorID-%x DeviceID-%x ClassCode-%x", Pci.Hdr.VendorId,Pci.Hdr.DeviceId,Pci.Hdr.ClassCode[0]);Print(L"
");}}return EFI_SUCCESS;}

 

从代码中可以看出,函数使用了 3 个 for 循环调用函数 PciDevicePresent(),依次寻找PCI/PCIE 设备是否存在。如果存在,则取出已经读取到的配置空间的数据,将设备的一些信息打印出来。

使用 EFI_PCI_IO_PROTOCOL 遍历设备则比较简单,因为之前所得到的此 Protocol 的实例,就是为 PCI/PCIE 设备产生的,实际上相当于找到了设备,只需要将设备的信息打印出来即可。相应的代码见示例 7-3 所示。

【示例 7-3】使用 EFI_PCI_IO_PROTOCOL 遍历 PCI/PCIE 设备。

 

EFI_STATUS ListPCIMessage2(void){UINTN i,count=0;PCI_TYPE00 Pci;for(i=0;iPci.Read(gPCIIOArray[i],EfiPciWidthUint32,0,sizeof (PCI_TYPE00) / sizeof (UINT32),&Pci);++count;Print(L"%02d. VendorID-%x DeviceID-%x ClassCode-%x", count,Pci.Hdr.VendorId,Pci.Hdr.DeviceId,Pci.Hdr.ClassCode[0]);Print(L"
");}return EFI_SUCCESS;}

 

本节所准备的示例,主要是为了演示如何使用与 PCI/PCIE 相关的两个 Protocol。代码本身还有许多不完善的地方,比如对多个总线域情况的处理、内存的释放、Protocol 的关闭等,都没有考虑。本书的代码,包括本节的代码在内,建议读者只用来学习使用,如果想商用,则应该在代码中将所有情况考虑到。

可参照 2.1.3 节的方法,设置编译的环境变量,并使用如下命令编译程序:

 

C:UEFIWorkspaceedk2uild -p RobinPkgRobinPkg.dsc -m RobinPkgApplicationsListPCIMsg ListPCIMsg.inf -a X64

 

所编译的程序最好在实际的机器上测试运行。笔者使用 2.2.2 节搭建的 QEMU 环境来运行编译好的 64 位 UEFI 程序,程序运行的结果如图 7-6 所示。

代码

图 7-6    测试 ListPCIMsg 程序 

      审核编辑:彭静
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分