BIOS在POST(Power-on Self Test)阶段,会扫描pci设备是否有expansion rom,有的话将其拷贝到ram中执行。在PCI规范中称为expansion rom,在BIOS术语里面称为option rom
Expansion rom base address
扩展空间桥
PCI_EXPANSION_ROM_BASE 0X30
1)PCIE枚举的时候,侦测到该PCIE设备;
(2)检查该PCIE设备是否包含有合法的Option Rom,即符合业界Option Rom标准规范,合法的关键字为0xaa55;如果有,则执行步骤(3),如果没有则执行步骤(6);
(3)从步骤(2)得知含有合法的Option Rom,再进一步分析该Option Rom:从中找到关键字“PCIR”处的地址,然后读取该地址处有关Option Rom类型的字段type,如果类型type字段为0x03,则表明为UEFI Option Rom,则执行步骤(4);如果该字段不是0x03,则执行步骤(6);
(4)拷贝UEFI Option Rom到内存中;
(5)运行拷贝到内存中的UEFI Option Rom;
像PCI配置空间 Expansion rom base address 写0xfffffffe
然后返回基本的rom大小信息,保存在RomSize变量里面。
当返回的地址不是0或者0xffffffffe
就认为该PCI的opronrom寄存器有配置过 返回的大小
PCI扫描过程中是如何确定PCI设备是支持option rom的?
InitializePciDevices->EnumerateBus->QueryPciDevice-> GetOptRomRequirements
Option Rom驱动后面是如何被处理的?
InstallPciDevice
Device List Pointer: points to the list of Device IDs supported by the Expansion ROM image
图1 UEFI Option ROM结构(From UEFI Spec 2.8 page 723)
毕竟都是脱胎于PCI/PCIE规范,和Legacy Option ROM的结构是相同的。UEFI Option ROM利用了之前保留的字节(偏移0x04处),用来表明自己的身份。
1 UEFI Option ROM的加载过程
Option ROM的执行文件不是在Flash上运行的,它会被拷贝到内存中,然后在内存中执行。Legacy BIOS的Option ROM一般加载到0xC0000~0xE0000(即0xC000段至0xE000段),而UEFI Option ROM并没有这样的约定。
仔细读读ProcessOpRomImage()的代码,可以窥见处理Option Rom的过程。
ProcessOpRomImage()的入口参数只有一个:PCI_IO_DEVICE *PciDevice。这是一个指向PCI设备的指针,包含了设备的所有属性以及其兄弟设备、父设备的信息。数据结构PCI_IO_DEVICE定义在PciBus.h中,可以对照PCI协议去理解。
函数中用do-while循环对设备进行循环处理,寻找到Option ROM的ROM signature(也即0xAA55)。而后对ROM结构进行分析,包括是否为EFI image、机器类型是否支持等,并创建其设备路径(DevicePath)。
在UEFI中,使用Device Path去描述一个设备的位置信息,总线、启动项等也常用它来描述。进入UEFI shell的时候,出现的各设备的字符串描述,就是Device Path:
Device Path共有六种,在Uefi Spec中有详细的描述
typedef struct {
UINT32 Signature; ///< "PCIR"
UINT16 VendorId;
UINT16 DeviceId;
UINT16 Reservd0;
UINT16 Length;
UINT8 Revision;
UINT8 ClassCode[3];
UINT16 ImageLength;
UINT16 CodeRevision;
UINT8 CodeType;
UINT8 Indicator;
UINT16 Reserved1;
} PCI_DATA_STRUCTURE;
函数中创建的Option ROM device path类型为MEDIA_DEVICE_PATH,这是一种能够作为启动项设备的Device Path。
创建完成后,调用LoadImage()和StartImage(),执行Option ROM代码。
1.GetOpRomInfo(IN OUT PCI_IO_DEVICE *PciIoDevice) //获取Option rom的信息 判断PCI设备是否支持Optionrom 支持写入
0xfffffffe返回option rom的RomSize
2.ContainEfiImage(
IN VOID *RomImage,
IN UINT64 RomSize
)
check if the RomImage contains EFI Images.
#define PCI_DATA_STRUCTURE_SIGNATURE SIGNATURE_32 ('P', 'C', 'I', 'R')
#define PCI_CODE_TYPE_EFI_IMAGE 0x03 //EFIimage
RomPcir= (PCI_DATA_STRUCTURE *)((UINT8 *)RomHeader + RomHeader->PcirOffset);
if (RomPcir->Signature != PCI_DATA_STRUCTURE_SIGNATURE) {
break;
}
if (RomPcir->CodeType == PCI_CODE_TYPE_EFI_IMAGE) {
return TRUE;
}
3.InitializePciLoadFile2()->LocalLoadFile2
@param RomBase Base address of ROM driver loaded from.
4.
LoadOpRomImage (
IN PCI_IO_DEVICE *PciDevice,
IN UINT64 RomBase
) //
5.
ProcessOpRomImage (
IN PCI_IO_DEVICE *PciDevice
)
option rom Image需要512字节对齐 循环寻找所有的Image
FV拓展 EFI OPTIONROM
2 如何生成UEFI Option ROM
UEFI Option ROM实际上是UEFI driver的一种,EDKII提供了相应的工具,将生成文件转换为Option ROM。前面已经说过了,我们现在所开发的option ROM,主要是PCI Option ROM。关于PCI Option ROM的内容可以参考《EDKII Driver Writer’s Guide for UEFI 2.3.1》,以下的介绍也主要来自于这个文档。
有两种方法可以生成PCI Option ROM镜像,使用工具EfiRom转换或直接使用EDKII的INF/FDF文件编译生成。
EfiRom提供了源代码,允许用户在任何支持EDKII的操作系统上编译。源代码位于\BaseTools\Source\C\EfiRom下,在我开发用的机器上(Win10),编译后的执行文件位于\BaseTools\Bin\Win32。
ENTRY_POINT = FatEntryPoint
UNLOAD_IMAGE = FatUnload
PCI_VENDOR_ID = 0x1d94
PCI_DEVICE_ID = 0x8086
PCI_CLASS_CODE = 0x03
PCI_COMPRESS =TURE
Loadpcirom xxx.rom 判断loadrom是否是option rom 的header AA55
((Pcir->CodeType == PCI_CODE_TYPE_EFI_IMAGE)
EFI_PCI_EXPANSION_ROM_HEADER_EFISIGNATURE = 0x0EF1
UEFI Option ROM是通过UEFI Driver转换而来的,至于如何编写UEFI driver,那是另外一个议题,这里不展开。
EfiRom会对传入的efi文件(UEFI Driver)进行验证,比如Rom头是不是0xAA55、PCI数据结构标识是不是“PCIR”等。任何一项检查不通过,则EfiRom会退出,创建Option ROM的过程将被终止。
生成命令如下:
EfiRom -f 0x9999 -i 0x8000 -e pcidriver.efi
其中,-f后指定Verdor ID,-i后指定 Device ID,-e之后给定需要转换的文件。更多的转换方法,包括如何与Legacy Option ROM一起打包转换、如何压缩等,请参考之前提到的手册《EDKII Driver Writer’s Guide for UEFI 2.3.1》第18章第7节。
另一种转换方法是使用INF/FDF,在build命令执行的时候,自动调用efirom将其转换为指定的Option ROM。这是我常用的方法,编译的同时就完成了转换过程,一个典型的INF例子如下:
Vendor ID和Device ID可以在Inf文件中指定,其他包括PCI类码、PCI版本、是否对Option ROM进行压缩(PCI_COMPRESS)都可以指定。
不管是采用EfiRom工具直接转换,还是使用Inf文件,都只能对一个UEFI Driver进行处理。如果需要同时管理多个UEFI Driver,以及生成多种类型的Option ROM(IA32、X64等),可以使用FDF文件进行处理。具体的内容就不一一讨论了,同样可以参考上述的编程手册。
成功制作optionrom 文件在EFIshell 下使用Loadpcirom file 成功
但是发现start image 是不会执行到UEFI driver的Entry point 会执行DriverBingProtocol,还存在问题不会去执行option rom的Entry point,原因怀疑为fat.inf中包含的Lib无法使用导致Entry point无法执行,使用hellowworld.rom后成功执行Entripoint 代码
UefiMain.dll文件时使用了/dll/entry:_ModuleEntryPoint。.efi文件是遵循PE32格式的二进制文件,_ModuleEntryPoint是这个二进制文件的入口。
在Shell中执行UefiMain.efi时,Shell首先用gBS->LoadImage()将UefiMain.efi文件加载到内存生成Image对象,然后调用gBS->StartImage(Image)启动这个Image
、
所开发的Option ROM是遵循UEFI driver module的,实际上也算是PCI Driver,所以它必须实现EFI_DRIVER_BINDING_PROTOCOL,并实例化Supported(), Start(), and Stop() 这三个服务。
前面已经探讨过如何启动Option ROM了,目前我们主要关心在哪里加上实际执行的代码。
大部分执行代码是在Start()中添加的,Start()主要的任务是启动硬件设备,在函数中最重要的事情是调用InstallProtocolInterface()或者InstallMultipleProtocolInterfaces()在ControllerHandle上安装驱动Protocol。
而Stop()函数用来卸载驱动,并停止硬件设备,基本上不需要修改BlankDrv提供的原始代码。
Supported()用来评估传递给Driver的device handle所指明的pci controller是否能被driver所驱动,主要通过Vendor ID、Device ID、Class code判定。
如前所述,具体的驱动细节可以参考其他的文档,架构搭建完后,就可以专注在Option ROM的功能实现上了。之前35篇博客中所讨论的议题,所编写的代码,完全可以移植到Option ROM中,只要硬件设备的扩展ROM大小足够。
开发Option ROM
Option rom的驱动driver 需要满足PCI的规范 不能是任意的UEFI Driver
加载option rom需要判断是否为目标PCIE设备
Driver image需要从一些媒介上加载,如ROM, FLASH, harddrives, floppy drives, CD-ROM甚至是network controller,当发现了driver image,就会通过loadimage()加载到system memory中,并创建一个handle,handle上挂载Loaded Image Protocol 实例,从而这个handle称为Image
Handle,此时driver并没有started,等待startimage()调用
loadimage()
*第一步:将UefiMain.efi文件加载到内存,生成Image对象,NewHandle为其句柄
句柄:
1、特殊的智能指针(当一个应用程序要引用其他系统管理的内存块或对象时)
2、Windows编程的基础。句柄指的是使用一个唯一的4字节型整数值,来标识应用程序中
的不同对象和同类中的不同示例。应用程序能通过句柄访问相应对象的信息,但这种句柄
不是指针,应用程序不能通过句柄直接阅读文件中的信息。句柄是Windows系统用来标识
应用程序中建立的或是使用的资源的唯一整数。
使用gBS->LoadImage()函数,将加载结果返回给Status参数,同时改变NewHand的值。
使用EFI_ERROR()函数判断是否加载成功。
*/
Status = gBS->LoadImage(
FALSE,
*ParentImageHandle,
(EFI_DEVICE_PATH_PROTOCOL*)DevicePath,
NULL,
0,
&NewHandle);
ImageHandle: 选择start的image handle
imagehandle在经startimage()执行后可能结构如下,一个挂载有driver binding protocol实例的image handle 称为Driver Image Handle
Startimage()
在gBS->StartImage中,SetJump/LongJump为应用程序的执行提供了一种错误处理机制,执行流程如图所示:
就是调用各个driver image 的入口函数,入口函数必须符合UEFI Driver Model,不接触硬件,只能在自己的image handle上install protocol 实例,如Driver Binding Protocol,ComponentName Protocol可选择性install;
Build efi的指令
DEBUG_VS2008x86_IA32_DLINK_FLAGS = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /MACHINE:X86 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG
RELEASE_VS2008x86_IA32_DLINK_FLAGS = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /IGNORE:4254 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /MACHINE:X86 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /MERGE:.data=.text /MERGE:.rdata=.text
由图可以看出,连接器在生产UefiMain.dll文件时使用了/dll/entry:_ModuleEntryPoint.efi文件是遵循PE32格式的二进制文件,_ModuleEntryPoint是这个二进制文件的入口。
- 调用CoreLoadImage,从FV中load DXE image 到memory中, 应该是code中定义的FFS(DXE module),编译生成的PE/COFF格式image,load成功生成ImageHandle
- Option Rom内置在设备中
- Option Rom在UEFI引导下自动加载
- EFI Option Rom包括作为设备驱动程序的EFI图像
- ROM报头有0x0EF1作为签名
- PCI数据结构包括DeviceId和VendorId
- 选项rom图像可以包含多个图像,以支持不同的ARCHs。