0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

Linux阅码场 来源:Linux阅码场 作者:Linux阅码场 2022-08-04 09:38 次阅读

目录

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 所示。

a19e40d0-1389-11ed-ba43-dac502259ad0.png

图 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 设备的索引

a1b5677e-1389-11ed-ba43-dac502259ad0.png

图 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设备的基本配置空间。

a1ca30c8-1389-11ed-ba43-dac502259ad0.png

图 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个 BaseAddressRegiste(r基址寄存器)。这组寄存器被称为 BAR,保存了 PCI 设备使用的地址空间的基地址,也即该设备在 PCI 总线域中的地址。每个 PCI 设备最多可以有 6 个基址空间,但多数设备不会使用这么多,笔者以前常用的南京沁恒的 CH366 芯片,只使用了第一个 BAR。

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

a1dd53b0-1389-11ed-ba43-dac502259ad0.png

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

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

a1e92492-1389-11ed-ba43-dac502259ad0.png

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

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

a20f9dde-1389-11ed-ba43-dac502259ad0.png

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

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

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

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 函数接口

typedefstruct_EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL{EFI_HANDLEParentHandle;//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空间的接口

typedefstruct{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,  //读写的数据个数,单位为读写宽度WidthINOUTVOID*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访问配置空间的接口

typedefstruct{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为单位INOUTVOID*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 总线域(PCISegment),即一个主桥。因此,在使用 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 所示。

a232f18a-1389-11ed-ba43-dac502259ad0.png

图 7-6 测试 ListPCIMsg 程序

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

    关注

    33

    文章

    7979

    浏览量

    149258
  • 软件
    +关注

    关注

    68

    文章

    4446

    浏览量

    86245
  • 代码
    +关注

    关注

    30

    文章

    4603

    浏览量

    67373
  • UEFI
    +关注

    关注

    0

    文章

    51

    浏览量

    11777

原文标题:《UEFI编程实践》选载之访问 PCI/PCIE 设备

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    【飞腾派4G版免费试用】飞腾派运行uefi固件,加载通用操作系统

    就尝试一在飞腾派上跑uefi看看效果如何。 先说结论:可以正常引导,我使用的飞腾E2000 uefi+VKYLIN系统进行的尝试,使用体验上还需要完善。固件适配上还有些功能需要打磨,等后面有时间了在
    发表于 01-11 12:35

    ARM系统预引导固件的新机遇-UEFI

    是作为固件在UEFI规范的界面层之下提供计算机系统初始化服务。PI的实现不是UEFI规范的必须。  UE
    发表于 08-23 09:06

    uefi的标准哪里可以下载呀?

    uefi的标准哪里可以下载呀?
    发表于 11-06 16:49

    BOXNUC7I3BNK将EVO 970视为Legacy而非UEFI

    有一个新的BOXNUC7I3BNK NUC套件。仅使用三星EVO 970 M.2 ssd。 BIOS将驱动器识别为M.2 ssd,但是在启动面板中,不会将其识别为UEFI驱动器,仅作为Legacy
    发表于 11-07 11:08

    UEFI有什么定义?

     EFI的出现第一次被正式提出,是在2000年的Intel春季IDF上,经过几次修订,现在已经到了EFI 1.10版,而2.0版正由UEFI这个组织制定中(故EFI也现称为UEFI)。
    发表于 10-30 09:12

    如何切换BIOS启动与UEFI启动 bios与uefi切换方法

    就修改,没有就跳过:1)切换到Boot,选择UEFI Boot回车设置为Enabled。2)有些电脑在Startup,把UEFI/Legacy Boot设置为UEFI Only。3)
    发表于 06-05 11:54

    Embedded SIG | 树莓派的UEFI支持和网络启动

    。使用 openEuler Embedded UEFI+GRUB 的树莓派镜像openEuler Embedded 的树莓派镜像集成了基于树梅派 4B 的混合部署环境依赖,因此建议直接使用 openEuler
    发表于 09-07 15:22

    使用VisionFive 2适配UEFI有哪些资料可以参考?

    使用VisionFive 2适配UEFI,有哪些资料可以参考?
    发表于 09-12 06:52

    基于UEFI固件的攻击检测系统的设计与实现

    计算机基础硬件和系统软件的桥梁。UEFI (Unified Extensible Firmware Interface,统一可扩展固件接口)是新的固件标准,目前被业界广泛使用。UEFI为固件和操作系统之间的
    发表于 11-30 17:31 0次下载
    基于<b class='flag-5'>UEFI</b>固件的攻击检测系统的设计与实现

    uefi 嵌入式Linux,面向嵌入式平台的高级UEFI开发环境.PDF

    面向嵌入式平台的高级UEFI开发环境面向嵌入式平台的高级 UEFI 开发环境晋磊, 技术市场工程师, 英特尔周鹏程, 开发经理, 百敖软件*姜波, 首席技术官, 盛博科技*PTAS00
    发表于 11-02 13:06 13次下载
    <b class='flag-5'>uefi</b> 嵌入式Linux,面向嵌入式平台的高级<b class='flag-5'>UEFI</b>开发<b class='flag-5'>环境</b>.PDF

    如何使树莓派 4B 支持 UEFI

    混合部署的从核启停依赖 UEFI 第三方固件支持 PSCI 标准实现,本文介绍如何使树莓派 4B 支持 UEFI,并可通过 SD 卡或网络启动 openEuler Embedded。
    的头像 发表于 09-07 11:26 2026次阅读

    龙芯LoongArch获国际主流固件接口组织UEFI全面支持

    。 LoongArch基础代码被TianoCore EDK2合并进主线 UEFI(Unified Extensible Firmware Interface)即统一可扩展固件接口,是一种个人电脑系统规格,可扩展固件接口、负责加电
    的头像 发表于 10-20 18:14 945次阅读
    龙芯LoongArch获国际主流固件<b class='flag-5'>接口</b>组织<b class='flag-5'>UEFI</b>全面支持

    P7固件 FCODE BIOS UEFI

    电子发烧友网站提供《P7固件 FCODE BIOS UEFI.zip》资料免费下载
    发表于 08-08 11:24 1次下载
    P7固件 FCODE BIOS <b class='flag-5'>UEFI</b>

    研华SSD与Phoenix合作开发基于UEFI安全解决方案

     研华与BIOS固件专家Phoenix合作推出基于UEFI(Unified Extensible Firmware Interface统一可扩展固件接口)的SSD安全软件工具-- SQErase
    发表于 09-06 13:44 221次阅读
    研华SSD与Phoenix合作开发基于<b class='flag-5'>UEFI</b>安全解决方案

    UEFIRC:运行于UEFI环境下的IRC聊天室

    据悉,开源开发者Phillip Tennen展示了基于UEFI的沉浸式IRC网络聊天室:UREFIRC原型设计。该设计无需进入操作系统,仅在UEFI环境内运行。
    的头像 发表于 04-08 16:16 414次阅读