《UEFI编程实践》选载之访问PCI/PCIE设备

原创 Linux阅码场 2022-08-04 08:00



作者简介

罗冰:系统安全(特别是物理隔离领域)专家,主导开发网络隔离卡、双网隔离机、国产隔离系统、单向光传输等各类安全产品,拥有十几项发明和实用新型专利。致力于UEFI技术的研究、实践和推广,在CSDN和知乎上设有“UEFI开发探索”专栏。译著《现代X86汇编语言程序设计》、《21世纪机器人》,目前就职于国内某网络安全公司,担任总工程师。


目录

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/PCIESMBus、串口等,UEFI 都已经提供了支持。对于依赖于 BIOS 接口进行产品开发的厂商来说,这是一个非常好的消息,产品的开发速度将大幅提高,稳定性和兼容性也能得到保障。


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


7.1 访问 PCI/PCIE 设备


PCIPeripheral 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 BusPCI 主机总线,用于完成 CPU PCI 设备之间的数据交换。PCI Root BridgePCI 根桥一般也只有一个,它管理一个局部总线,下挂一棵 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 Register  基址寄存器这组寄存器被称为 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 BridgePCI 主桥)控制器驱动,另一个是 PCI 总线驱动。这两个模块是和特定的平台硬件绑定的,在这种机制下,屏蔽了不同的 CPU 架构差异,为软件开发者提供了比较一致的 Protocol 接口。下一节详细介绍访问 PCI/PCIE 设备的 Protocol


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


UEFI 标准中提供了两类访问 PCI/PCIE 设备的 ProtocolEFI_PCI_ROOT_BRIDGE_ IO_PROTOCOL EFI_PCI_IO_PROTOCOL。前者为 PCI 根桥提供了抽象的 IO 功能,它 PCI Host Bus ControllerPCI 主总线驱动器产生,一般由 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\ Include\Protocol\PciRootBridgeIo.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 实例,直接访问每个实例的配置空间,将其信息打印出来。


本节提供的示例程序位于随书代码的 RobinPkg\Applications\ListPCIMsg 目录下。示例


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\n",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\n",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"\n");}}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;i{gPCIIOArray[i]->Pci.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"\n");}return EFI_SUCCESS;}


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


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


C:\UEFIWorkspace\edk2\build -p RobinPkg\RobinPkg.dsc \-m RobinPkg\Applications\ListPCIMsg\ ListPCIMsg.inf -a X64


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

7-6    测试 ListPCIMsg 程序


第七章完整内容可点击下面链接或识别二维码查看


https://oxz.xet.tech/s/T4gOj


如需进群请添加小月微信邀请:linuxer2016


Ftrace训练营火热报名中:Ftrace训练营:站在设计者的角度来理解ftrace(限50人)。训练营第一期报名已圆满成功,好评如潮。第二期课程正在进行中,第三期报名正在火爆进行中(咨询小月微信:linuxer2016)。


ARM安全架构训练营2期火热报名中:阅码场训练营:ARM安全架构之Trustzone-TEE实战报名咨询客服(小月微信:linuxer2016)。


ARM架构与调优调试训练营火热报名中:阅码场训练营:ARM架构与调试调优。报名咨询客服(小月微信:linuxer2016)。




Linux阅码场 专业的Linux技术社区和Linux操作系统学习平台,内容涉及Linux内核,Linux内存管理,Linux进程管理,Linux文件系统和IO,Linux性能调优,Linux设备驱动以及Linux虚拟化和云计算等各方各面.
评论
  • 实用性高值得收藏!! (时源芯微)时源专注于EMC整改与服务,配备完整器件 TVS全称Transient Voltage Suppre,亦称TVS管、瞬态抑制二极管等,有单向和双向之分。单向TVS 一般应用于直流供电电路,双向TVS 应用于电压交变的电路。在直流电路的应用中,TVS被并联接入电路中。在电路处于正常运行状态时,TVS会保持截止状态,从而不对电路的正常工作产生任何影响。然而,一旦电路中出现异常的过电压,并且这个电压达到TVS的击穿阈值时,TVS的状态就会
    时源芯微 2025-01-16 14:23 74浏览
  • 一个易用且轻量化的UI可以大大提高用户的使用效率和满意度——通过快速启动、直观操作和及时反馈,帮助用户快速上手并高效完成任务;轻量化设计则可以减少资源占用,提升启动和运行速度,增强产品竞争力。LVGL(Light and Versatile Graphics Library)是一个免费开源的图形库,专为嵌入式系统设计。它以轻量级、高效和易于使用而著称,支持多种屏幕分辨率和硬件配置,并提供了丰富的GUI组件,能够帮助开发者轻松构建出美观且功能强大的用户界面。近期,飞凌嵌入式为基于NXP i.MX9
    飞凌嵌入式 2025-01-16 13:15 62浏览
  • 故障现象 一辆2007款法拉利599 GTB车,搭载6.0 L V12自然吸气发动机(图1),累计行驶里程约为6万km。该车因发动机故障灯异常点亮进厂检修。 图1 发动机的布置 故障诊断接车后试车,发动机怠速轻微抖动,发动机故障灯长亮。用故障检测仪检测,发现发动机控制单元(NCM)中存储有故障代码“P0300 多缸失火”“P0309 气缸9失火”“P0307 气缸7失火”,初步判断发动机存在失火故障。考虑到该车使用年数较长,决定先使用虹科Pico汽车示波器进行相对压缩测试,以
    虹科Pico汽车示波器 2025-01-15 17:30 44浏览
  • 电竞鼠标应用环境与客户需求电竞行业近年来发展迅速,「鼠标延迟」已成为决定游戏体验与比赛结果的关键因素。从技术角度来看,传统鼠标的延迟大约为20毫秒,入门级电竞鼠标通常为5毫秒,而高阶电竞鼠标的延迟可降低至仅2毫秒。这些差异看似微小,但在竞技激烈的游戏中,尤其在对反应和速度要求极高的场景中,每一毫秒的优化都可能带来致胜的优势。电竞比赛的普及促使玩家更加渴望降低鼠标延迟以提升竞技表现。他们希望通过精确的测试,了解不同操作系统与设定对延迟的具体影响,并寻求最佳配置方案来获得竞技优势。这样的需求推动市场
    百佳泰测试实验室 2025-01-16 15:45 62浏览
  • 食物浪费已成为全球亟待解决的严峻挑战,并对环境和经济造成了重大影响。最新统计数据显示,全球高达三分之一的粮食在生产过程中损失或被无谓浪费,这不仅导致了资源消耗,还加剧了温室气体排放,并带来了巨大经济损失。全球领先的光学解决方案供应商艾迈斯欧司朗(SIX:AMS)近日宣布,艾迈斯欧司朗基于AS7341多光谱传感器开发的创新应用来解决食物浪费这一全球性难题。其多光谱传感解决方案为农业与食品行业带来深远变革,该技术通过精确判定最佳收获时机,提升质量控制水平,并在整个供应链中有效减少浪费。 在2024
    艾迈斯欧司朗 2025-01-14 18:45 96浏览
  •   在信号处理过程中,由于信号的时域截断会导致频谱扩展泄露现象。那么导致频谱泄露发生的根本原因是什么?又该采取什么样的改善方法。本文以ADC性能指标的测试场景为例,探讨了对ADC的输出结果进行非周期截断所带来的影响及问题总结。 两个点   为了更好的分析或处理信号,实际应用时需要从频域而非时域的角度观察原信号。但物理意义上只能直接获取信号的时域信息,为了得到信号的频域信息需要利用傅里叶变换这个工具计算出原信号的频谱函数。但对于计算机来说实现这种计算需要面对两个问题: 1.
    TIAN301 2025-01-14 14:15 138浏览
  • 近期,智能家居领域Matter标准的制定者,全球最具影响力的科技联盟之一,连接标准联盟(Connectivity Standards Alliance,简称CSA)“利好”频出,不仅为智能家居领域的设备制造商们提供了更为快速便捷的Matter认证流程,而且苹果、三星与谷歌等智能家居平台厂商都表示会接纳CSA的Matter认证体系,并计划将其整合至各自的“Works with”项目中。那么,在本轮“利好”背景下,智能家居的设备制造商们该如何捉住机会,“掘金”万亿市场呢?重认证快通道计划,为家居设备
    华普微HOPERF 2025-01-16 10:22 76浏览
  • 全球领先的光学解决方案供应商艾迈斯欧司朗(SIX:AMS)近日宣布,与汽车技术领先者法雷奥合作,采用创新的开放系统协议(OSP)技术,旨在改变汽车内饰照明方式,革新汽车行业座舱照明理念。结合艾迈斯欧司朗开创性的OSIRE® E3731i智能LED和法雷奥的动态环境照明系统,两家公司将为车辆内饰设计和功能设立一套全新标准。汽车内饰照明的作用日益凸显,座舱设计的主流趋势应满足终端用户的需求:即易于使用、个性化,并能提供符合用户生活方式的清晰信息。因此,动态环境照明带来了众多新机遇。智能LED的应用已
    艾迈斯欧司朗 2025-01-15 19:00 49浏览
  • 百佳泰特为您整理2025年1月各大Logo的最新规格信息,本月有更新信息的logo有HDMI、Wi-Fi、Bluetooth、DisplayHDR、ClearMR、Intel EVO。HDMI®▶ 2025年1月6日,HDMI Forum, Inc. 宣布即将发布HDMI规范2.2版本。新规范将支持更高的分辨率和刷新率,并提供更多高质量选项。更快的96Gbps 带宽可满足数据密集型沉浸式和虚拟应用对传输的要求,如 AR/VR/MR、空间现实和光场显示,以及各种商业应用,如大型数字标牌、医疗成像和
    百佳泰测试实验室 2025-01-16 15:41 52浏览
  • 数字隔离芯片是现代电气工程师在进行电路设计时所必须考虑的一种电子元件,主要用于保护低压控制电路中敏感电子设备的稳定运行与操作人员的人身安全。其不仅能隔离两个或多个高低压回路之间的电气联系,还能防止漏电流、共模噪声与浪涌等干扰信号的传播,有效增强电路间信号传输的抗干扰能力,同时提升电子系统的电磁兼容性与通信稳定性。容耦隔离芯片的典型应用原理图值得一提的是,在电子电路中引入隔离措施会带来传输延迟、功耗增加、成本增加与尺寸增加等问题,而数字隔离芯片的目标就是尽可能消除这些不利影响,同时满足安全法规的要
    华普微HOPERF 2025-01-15 09:48 121浏览
  • 随着智慧科技的快速发展,智能显示器的生态圈应用变得越来越丰富多元,智能显示器不仅仅是传统的显示设备,透过结合人工智能(AI)和语音助理,它还可以成为家庭、办公室和商业环境中的核心互动接口。提供多元且个性化的服务,如智能家居控制、影音串流拨放、实时信息显示等,极大提升了使用体验。此外,智能家居系统的整合能力也不容小觑,透过智能装置之间的无缝连接,形成了强大的多元应用生态圈。企业也利用智能显示器进行会议展示和多方远程合作,大大提高效率和互动性。Smart Display Ecosystem示意图,作
    百佳泰测试实验室 2025-01-16 15:37 48浏览
  • 晶台光耦KL817和KL3053在小家电产品(如微波炉等)辅助电源中的广泛应用。具备小功率、高性能、高度集成以及低待机功耗的特点,同时支持宽输入电压范围。▲光耦在实物应用中的产品图其一次侧集成了交流电压过零检测与信号输出功能,该功能产生的过零信号可用于精确控制继电器、可控硅等器件的过零开关动作,从而有效减小开关应力,显著提升器件的使用寿命。通过高度的集成化和先进的控制技术,该电源大幅减少了所需的外围器件数量,不仅降低了系统成本和体积,还进一步增强了整体的可靠性。▲电路示意图该电路的过零检测信号由
    晶台光耦 2025-01-16 10:12 42浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦