1.编写读取设备PCI信息的Application
代码参考罗斌大佬,博客地址:UEFI开发探索13 – 访问PCI/PCI-E设备1
感谢罗斌大佬的贡献,让我在学习UEFI的道路上站在了巨人的肩膀上。
代码:
#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/DebugLib.h>#include <Protocol/PciIo.h>
#include <Protocol/PciRootBridgeIo.h>
#include <IndustryStandard/Pci.h>#include <stdio.h>
#include <stdlib.h>EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *gPciRootBridgeIo;EFI_STATUS LocatePciRootBridgeIo(void);EFI_STATUS PciDevicePresent(IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL * PciRootBridgeIo,OUT PCI_TYPE00 * Pci,IN UINT8 Bus,IN UINT8 Device,IN UINT8 Func);EFI_STATUS ListPciInformation(void);int main(IN int Argc, IN char **Argv)
{EFI_STATUS Status;PCI_TYPE00 Pci;Status = LocatePciRootBridgeIo();if(EFI_ERROR(Status)){printf("Call LocatePciRootBridgeIo failed,Can't find protocol!\n");}else{printf("Call LocatePciRootBridgeIo successed,Find protocol!\n");}ListPciInformation();return 0;
}EFI_STATUS LocatePciRootBridgeIo()
{EFI_STATUS Status;EFI_HANDLE *PciHandleBuffer = NULL;UINTN HandleIndex = 0;UINTN HandleCount = 0;Status = gBS->LocateHandleBuffer(ByProtocol,&gEfiPciRootBridgeIoProtocolGuid,NULL,&HandleCount,&PciHandleBuffer //二级指针);if(EFI_ERROR(Status)) return Status;for(HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++){Status = gBS->HandleProtocol(PciHandleBuffer[HandleIndex],&gEfiPciRootBridgeIoProtocolGuid,(VOID **)&gPciRootBridgeIo);if(EFI_ERROR(Status)) continue;else return EFI_SUCCESS;}return Status;}EFI_STATUS ListPciInformation()
{EFI_STATUS Status = EFI_SUCCESS;PCI_TYPE00 Pci;UINT16 Dev,Func,Bus,PciDevicecount = 0;printf("PciDeviceNo\tBus\tDev\tFunc | Vendor.Device.ClassCode\n");for(Bus=0; Bus<64; Bus++)for(Dev=0; Dev<= PCI_MAX_DEVICE; Dev++)for(Func=0; Func<=PCI_MAX_FUNC; Func++){Status = PciDevicePresent(gPciRootBridgeIo,&Pci,(UINT8)Bus,(UINT8)Dev,(UINT8)Func);if(Status == EFI_SUCCESS){PciDevicecount++;printf("%d\t%x\t%x\t%x\t",PciDevicecount,(UINT8)Bus,(UINT8)Dev,(UINT8)Func);printf("%x\t%x\t%x\n",Pci.Hdr.VendorId,Pci.Hdr.DeviceId,Pci.Hdr.ClassCode[0]);}}return EFI_SUCCESS;
}EFI_STATUS
PciDevicePresent (IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *PciRootBridgeIo,OUT PCI_TYPE00 *Pci,IN UINT8 Bus,IN UINT8 Device,IN UINT8 Func)
{UINT64 Address;EFI_STATUS Status;//// Create PCI address map in terms of Bus, Device and Func//Address = EFI_PCI_ADDRESS (Bus, Device, Func, 0);//// Read the Vendor ID register//Status = PciRootBridgeIo->Pci.Read (PciRootBridgeIo,EfiPciWidthUint32,Address,1,Pci);if (!EFI_ERROR (Status) && (Pci->Hdr).VendorId != 0xffff) {//// Read the entire config header for the device//Status = PciRootBridgeIo->Pci.Read (PciRootBridgeIo,EfiPciWidthUint32,Address,sizeof (PCI_TYPE00) / sizeof (UINT32),Pci);return EFI_SUCCESS;}return EFI_NOT_FOUND;
}
注意:
这是我这篇博客之前的描述,这个描述不对!下面是修改过的:
在LocatePciRootBridgeIo函数中根据guid利用LocateHandleBuffer找到支持gEfiPciRootBridgeIoProtocolGuid的所有handle,再调用HandleProtocol从handle数组里寻找安装了protocol的handle,找到了就会通过参数传递给定义的全局变量EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *gPciRootBridgeIo中。接下来就可以利用全局变量EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *gPciRootBridgeIo查询Pci设备的信息了。
查询并显示PCI信息的函数是ListPciInformation,函数中主要用PciDevicePresent函数查询PCI的信息。
在PciDevicePresent中,先创造PCI的地址映射,再根据映射的地址和gPciRootBridgeIo,读取PCI的信息到参数Pci中,然后在验证返回值是否正确,验证VendorId是否有效(当VendorID寄存器为0xFFFF时,为无效VendorID)。验证成功后读取PCI设备的整个配置头的信息,配置头信息结构体为:
typedef struct {PCI_DEVICE_INDEPENDENT_REGION Hdr;PCI_DEVICE_HEADER_TYPE_REGION Device;
} PCI_TYPE00;
而我们在打印时只打印Hdr中的信息,Hdr中的信息都有:
typedef struct {UINT16 VendorId;UINT16 DeviceId;UINT16 Command;UINT16 Status;UINT8 RevisionID;UINT8 ClassCode[3];UINT8 CacheLineSize;UINT8 LatencyTimer;UINT8 HeaderType;UINT8 BIST;
} PCI_DEVICE_INDEPENDENT_REGION;
我们只打印VendorId,DeviceId和ClassCode[0]。
代码写好后将其编译为.efi文件,我编译的是64位的.efi文件,因为PCI信息的查看在windows的模拟环境下查看不了,我第一次查看PCI信息时是用DUET做了个启动盘,将.efi文件放在启动盘的根目录下,在我的笔记本上用U启动,查看PCI信息。后面我会放一张在实体机(即我的笔记本)上查看PCI信息的截图。
下面就是在qemu虚拟机上查看PCI信息的步骤了。
2.制作OVMF固件
参照戴正华《UEFI编程与原理》,制作OVMF固件的命令:
①制作64位OVMF固件:
②制作32位OVMF固件:
3.将生成的.efi文件用UltraISO制作成镜像文件
打开UltraISO,New->Disk Image,然后
点击OK,将.efi文件拖入UltraISO,Ctrl+s,保存为PCI.img,保存到和OVMF.fd相同的目录下。
4.用qemu运行OVMF固件并将PCI.img挂载到UEFIShell下
FS0就是我们挂载的PCI.img。
先用pci命令查看PCI信息:
然后运行我们生成的.efi文件:
到这里就成功在qemu上读取PCI信息了,撒花花。
最后,再附上再我笔记本上用U盘启动进入UEFIShell查看PCI信息的图片:
①运行.efi文件
②PCI命令
如有错误,欢迎指出。