《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虚拟化和云计算等各方各面.
评论
  • 一、SAE J1939协议概述SAE J1939协议是由美国汽车工程师协会(SAE,Society of Automotive Engineers)定义的一种用于重型车辆和工业设备中的通信协议,主要应用于车辆和设备之间的实时数据交换。J1939基于CAN(Controller Area Network)总线技术,使用29bit的扩展标识符和扩展数据帧,CAN通信速率为250Kbps,用于车载电子控制单元(ECU)之间的通信和控制。小北同学在之前也对J1939协议做过扫盲科普【科普系列】SAE J
    北汇信息 2024-12-11 15:45 84浏览
  •         在有电流流过的导线周围会感生出磁场,再用霍尔器件检测由电流感生的磁场,即可测出产生这个磁场的电流的量值。由此就可以构成霍尔电流、电压传感器。因为霍尔器件的输出电压与加在它上面的磁感应强度以及流过其中的工作电流的乘积成比例,是一个具有乘法器功能的器件,并且可与各种逻辑电路直接接口,还可以直接驱动各种性质的负载。因为霍尔器件的应用原理简单,信号处理方便,器件本身又具有一系列的du特优点,所以在变频器中也发挥了非常重要的作用。  &nb
    锦正茂科技 2024-12-10 12:57 76浏览
  • RK3506 是瑞芯微推出的MPU产品,芯片制程为22nm,定位于轻量级、低成本解决方案。该MPU具有低功耗、外设接口丰富、实时性高的特点,适合用多种工商业场景。本文将基于RK3506的设计特点,为大家分析其应用场景。RK3506核心板主要分为三个型号,各型号间的区别如下图:​图 1  RK3506核心板处理器型号场景1:显示HMIRK3506核心板显示接口支持RGB、MIPI、QSPI输出,且支持2D图形加速,轻松运行QT、LVGL等GUI,最快3S内开
    万象奥科 2024-12-11 15:42 71浏览
  • 我的一台很多年前人家不要了的九十年代SONY台式组合音响,接手时只有CD功能不行了,因为不需要,也就没修,只使用收音机、磁带机和外接信号功能就够了。最近五年在外地,就断电闲置,没使用了。今年9月回到家里,就一个劲儿地忙着收拾家当,忙了一个多月,太多事啦!修了电气,清理了闲置不用了的电器和电子,就是一个劲儿地扔扔扔!几十年的“工匠式”收留收藏,只能断舍离,拆解不过来的了。一天,忽然感觉室内有股臭味,用鼻子的嗅觉功能朝着臭味重的方向寻找,觉得应该就是这台组合音响?怎么会呢?这无机物的东西不会腐臭吧?
    自做自受 2024-12-10 16:34 141浏览
  •         霍尔传感器是根据霍尔效应制作的一种磁场传感器。霍尔效应是磁电效应的一种,这一现象是霍尔(A.H.Hall,1855—1938)于1879年在研究金属的导电机构时发现的。后来发现半导体、导电流体等也有这种效应,而半导体的霍尔效应比金属强得多,利用这现象制成的各种霍尔元件,广泛地应用于工业自动化技术、检测技术及信息处理等方面。霍尔效应是研究半导体材料性能的基本方法。通过霍尔效应实验测定的霍尔系数,能够判断半导体材料的导电类型、载流子浓度及载流子
    锦正茂科技 2024-12-10 11:07 64浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-10 16:13 109浏览
  • 智能汽车可替换LED前照灯控制运行的原理涉及多个方面,包括自适应前照灯系统(AFS)的工作原理、传感器的应用、步进电机的控制以及模糊控制策略等。当下时代的智能汽车灯光控制系统通过车载网关控制单元集中控制,表现特殊点的有特斯拉,仅通过前车身控制器,整个系统就包括了灯光旋转开关、车灯变光开关、左LED前照灯总成、右LED前照灯总成、转向柱电子控制单元、CAN数据总线接口、组合仪表控制单元、车载网关控制单元等器件。变光开关、转向开关和辅助操作系统一般连为一体,开关之间通过内部线束和转向柱装置连接为多,
    lauguo2013 2024-12-10 15:53 85浏览
  • 【萤火工场CEM5826-M11测评】OLED显示雷达数据本文结合之前关于串口打印雷达监测数据的研究,进一步扩展至 OLED 屏幕显示。该项目整体分为两部分: 一、框架显示; 二、数据采集与填充显示。为了减小 MCU 负担,采用 局部刷新 的方案。1. 显示框架所需库函数 Wire.h 、Adafruit_GFX.h 、Adafruit_SSD1306.h . 代码#include #include #include #include "logo_128x64.h"#include "logo_
    无垠的广袤 2024-12-10 14:03 71浏览
  • 近日,搭载紫光展锐W517芯片平台的INMO GO2由影目科技正式推出。作为全球首款专为商务场景设计的智能翻译眼镜,INMO GO2 以“快、准、稳”三大核心优势,突破传统翻译产品局限,为全球商务人士带来高效、自然、稳定的跨语言交流体验。 INMO GO2内置的W517芯片,是紫光展锐4G旗舰级智能穿戴平台,采用四核处理器,具有高性能、低功耗的优势,内置超微高集成技术,采用先进工艺,计算能力相比同档位竞品提升4倍,强大的性能提供更加多样化的应用场景。【视频见P盘链接】 依托“
    紫光展锐 2024-12-11 11:50 51浏览
  • 天问Block和Mixly是两个不同的编程工具,分别在单片机开发和教育编程领域有各自的应用。以下是对它们的详细比较: 基本定义 天问Block:天问Block是一个基于区块链技术的数字身份验证和数据交换平台。它的目标是为用户提供一个安全、去中心化、可信任的数字身份验证和数据交换解决方案。 Mixly:Mixly是一款由北京师范大学教育学部创客教育实验室开发的图形化编程软件,旨在为初学者提供一个易于学习和使用的Arduino编程环境。 主要功能 天问Block:支持STC全系列8位单片机,32位
    丙丁先生 2024-12-11 13:15 50浏览
  • 概述 通过前面的研究学习,已经可以在CycloneVGX器件中成功实现完整的TDC(或者说完整的TDL,即延时线),测试结果也比较满足,解决了超大BIN尺寸以及大量0尺寸BIN的问题,但是还是存在一些之前系列器件还未遇到的问题,这些问题将在本文中进行详细描述介绍。 在五代Cyclone器件内部系统时钟受限的情况下,意味着大量逻辑资源将被浪费在于实现较大长度的TDL上面。是否可以找到方法可以对此前TDL的长度进行优化呢?本文还将探讨这个问题。TDC前段BIN颗粒堵塞问题分析 将延时链在逻辑中实现后
    coyoo 2024-12-10 13:28 102浏览
  • 时源芯微——RE超标整机定位与解决详细流程一、 初步测量与问题确认使用专业的电磁辐射测量设备,对整机的辐射发射进行精确测量。确认是否存在RE超标问题,并记录超标频段和幅度。二、电缆检查与处理若存在信号电缆:步骤一:拔掉所有信号电缆,仅保留电源线,再次测量整机的辐射发射。若测量合格:判定问题出在信号电缆上,可能是电缆的共模电流导致。逐一连接信号电缆,每次连接后测量,定位具体哪根电缆或接口导致超标。对问题电缆进行处理,如加共模扼流圈、滤波器,或优化电缆布局和屏蔽。重新连接所有电缆,再次测量
    时源芯微 2024-12-11 17:11 79浏览
  • 本文介绍Linux系统(Ubuntu/Debian通用)挂载exfat格式U盘的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。修改对应的内核配置文件# 进入sdk目录cdrk3562_linux# 编辑内核配置文件vi./kernel-5.10/arch/arm64/configs/rockchip_linux_defconfig注:不清楚内核使用哪个defc
    Industio_触觉智能 2024-12-10 09:44 92浏览
  • 全球知名半导体制造商ROHM Co., Ltd.(以下简称“罗姆”)宣布与Taiwan Semiconductor Manufacturing Company Limited(以下简称“台积公司”)就车载氮化镓功率器件的开发和量产事宜建立战略合作伙伴关系。通过该合作关系,双方将致力于将罗姆的氮化镓器件开发技术与台积公司业界先进的GaN-on-Silicon工艺技术优势结合起来,满足市场对高耐压和高频特性优异的功率元器件日益增长的需求。氮化镓功率器件目前主要被用于AC适配器和服务器电源等消费电子和
    电子资讯报 2024-12-10 17:09 88浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦