代码参考罗斌大佬,博客地址:UEFI开发探索13 – 访问PCI/PCI-E设备1
感谢罗斌大佬的贡献,让我在学习UEFI的道路上站在了巨人的肩膀上。
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
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信息的步骤了。
参照戴正华《UEFI编程与原理》,制作OVMF固件的命令:
①制作64位OVMF固件:
②制作32位OVMF固件:
打开UltraISO,New->Disk Image,然后
点击OK,将.efi文件拖入UltraISO,Ctrl+s,保存为PCI.img,保存到和OVMF.fd相同的目录下。
FS0就是我们挂载的PCI.img。
先用pci命令查看PCI信息:
然后运行我们生成的.efi文件:
到这里就成功在qemu上读取PCI信息了,撒花花。
最后,再附上再我笔记本上用U盘启动进入UEFIShell查看PCI信息的图片:
①运行.efi文件
如有错误,欢迎指出。