UEFI开发探索36 – UEFI Option ROM

本文深入探讨UEFI OptionROM的开发流程,包括其结构、加载机制、生成方法及软件结构设计。通过对比Legacy BIOS OptionROM,阐述了UEFI环境下OptionROM的优势与处理过程。

(请保留-> 作者: 罗冰   https://blog.youkuaiyun.com/luobing4365)

Option ROM的开发不是一门显学,相关的资料也少得可怜。如果是在ODM厂商工作,或者做BIOS相关的工作,可以接触到很多相关的材料。不过,对我这种必须开发Option ROM,公司又不是相关行业的,非常少见。这也导致在开发Legacy BIOS的Oprom时,只能一点点摸索,遇到的问题如山之多。

UEFI下开发Option ROM相对好多了,一方面资料比较全面,EDK的文档组织还是比较明晰的;另一方面,它也提供了标准的工具,非常便利。我自己的测试卡是PCIE接口的,接下来讨论的Option ROM是针对PCI扩展ROM的(ISA ROM在UEFI spec中好像不支持了)。

UEFI的Option ROM结构与之前介绍的Legacy BIOS Oprom类似:

图1 UEFI Option ROM结构(From UEFI Spec 2.8 page 723)

毕竟都是脱胎于PCI/PCIE规范,和Legacy Option ROM的结构是相同的。UEFI Option ROM利用了之前保留的字节(偏移0x04处),用来表明自己的身份。

1 UEFI Option ROM的加载过程

在MdeModulePkg的源文件MdeModulePkg\Bus\Pci\PciBusDxe\PciOptionRomSupport.c中,可以窥见UEFI Option ROM的处理过程。源文件中包含了处理Option ROM的8个函数:

图2 UDK2018 MdeModulePkg中处理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:

图3 TianoCore模拟环境中的启动信息

Device Path共有六种,在Uefi Spec中有详细的描述:

图4 Device Path 分类(UEFI Spec 2.8 page 285)

函数中创建的Option ROM device path类型为MEDIA_DEVICE_PATH,这是一种能够作为启动项设备的Device Path。

创建完成后,调用LoadImage()和StartImage(),执行Option ROM代码。

图5 PciOptionRomSupport.c的ProcessOptionRomImage()

如何生成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。

它提供的功能如下:

图6 EfiRom功能列表

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例子如下:

图7 INF例子

Vendor ID和Device ID可以在Inf文件中指定,其他包括PCI类码、PCI版本、是否对Option ROM进行压缩(PCI_COMPRESS)都可以指定。

不管是采用EfiRom工具直接转换,还是使用Inf文件,都只能对一个UEFI Driver进行处理。如果需要同时管理多个UEFI Driver,以及生成多种类型的Option ROM(IA32、X64等),可以使用FDF文件进行处理。具体的内容就不一一讨论了,同样可以参考上述的编程手册。

软件结构

UEFI Option ROM实质上是UEFI Driver,因此最好找一个比较“纯洁”的Driver例子开始。在github的EDKII部分找了很久,始终没有找到合适的。EDKII现在提供了开发驱动的工具 UEFI Driver Wizard,我没有去使用过,也许很多示例都集成到这里面去了。

最终还是使用了我以前使用的BlankDrv,我一向以这个为基础构建UEFI Option ROM。它目前还可以在这个地方下载:

https://sourceforge.net/projects/edk2/files/EDK%20II%20Releases/Demo%20apps/

对于Driver的开发,《UEFI原理与编程》中说得很详细,值得好好研究一下。不过,我的目标是开发出UEFI Option ROM,很多细节不需要深究。

所开发的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大小足够。

图8 UEFI Option ROM code

编译及演示

因为涉及到公司商业上的原因,原始代码不能贴出。我正在考虑使用另外一种PCI-E芯片做测试卡,ROM空间大些,做一些相对复杂的功能,到时再将代码公开。

编译和写入到测试卡的过程,可以参考博客:“UEFI开发探索12 – Oprom测试板”。

这里把测试的结果再贴一次:

图9 UEFI Option ROM演示

至此,UEFI探索系列的主线-UEFI Option ROM的开发就完成了。

对我来说,真是个不错的经历。从决定写这个系列博客,到现在已经半年多了。每周都强迫自己坐到书桌旁开始敲字,从不习惯到成自然,现在都成为我每周必做的功课了。

在这个过程中,我完成了很多知识的补全。不过,感觉上了解越多,不理解的也越来越多。

计划中还有14篇,将针对各个我感兴趣的议题再进行整理。这个探索的旅程还远到没结束的时候。

<think>好的,用户的问题是关于UEFI读取PCIE Option ROM的示例代码或实现方法。首先,我需要回忆一下相关的UEFI开发知识,尤其是PCI/PCI-E设备的处理部分。根据用户提供的引用内容,特别是引用[1]和[3],UEFI Option ROM实际上是UEFI驱动的一种,而访问PCI/PCI-E设备需要定位设备、识别设备类型、访问内部寄存器等步骤。 接下来,我需要确定如何从PCI设备中读取Option ROM。根据引用[2],Option ROM的头部签名是0xAA55,并且需要被复制到内存中才能执行。所以,实现步骤可能包括定位PCI设备、检查是否存在Option ROM、读取ROM内容到内存等。 然后,参考引用[4],在UEFI中,PCI设备的BAR空间已经被枚举和解析,但分配资源可能需要进一步操作。因此,可能需要使用EDKII提供的PCI IO协议来访问设备的配置空间,特别是通过PCI设备的配置空间访问Option ROM的地址寄存器(通常是0x30偏移处)。 我需要考虑代码的结构,比如如何遍历PCI设备,如何检查每个设备的Option ROM是否存在,如何映射ROM到内存,以及如何读取其内容。同时,需要注意UEFI驱动的开发规范,比如使用正确的协议和函数,如PciIo->Mem.Read()来访问设备的内存空间。 可能还需要处理对齐和内存映射的问题,因为Option ROM的大小可能不是页面对齐的,需要使用AllocatePages或AllocatePool来分配内存。此外,验证ROM的签名是否正确(0xAA55)也是关键步骤。 最后,根据用户提供的引用,特别是引用[1]提到的EDKII文档和工具,可能需要指导用户查阅相关文档以获取更详细的实现方法,或者提供示例代码的框架,如使用PciIo协议访问配置空间,读取ROM基地址,映射并复制数据到内存中。 需要注意代码的正确性和符合UEFI规范,比如正确使用EFI_STATUS处理错误,以及确保内存操作的安全性。同时,要提醒用户Option ROM在运行前必须复制到RAM中,不能直接在Flash上运行,如引用[2]所述。 总结步骤:定位PCI设备→访问配置空间检查ROM存在→映射ROM到内存→读取并验证内容→处理资源分配。然后根据这些步骤构建示例代码的框架,并引用相关的参考资料。</think>根据UEFI开发规范,读取PCIE Option ROM的实现主要涉及PCI配置空间操作和内存映射。以下是关键步骤及示例框架: ### 一、PCI设备定位与配置空间访问 1. 使用$gBS->LocateHandleBuffer()$定位所有PCI设备控制器 2. 通过$OpenProtocol()`获取`EFI_PCI_IO_PROTOCOL`接口[^3] ```c EFI_STATUS Status; EFI_HANDLE *ControllerHandleBuffer; UINTN ControllerHandleCount; Status = gBS->LocateHandleBuffer( ByProtocol, &gEfiPciIoProtocolGuid, NULL, &ControllerHandleCount, &ControllerHandleBuffer ); for (UINTN i = 0; i < ControllerHandleCount; i++) { EFI_PCI_IO_PROTOCOL *PciIo; Status = gBS->OpenProtocol( ControllerHandleBuffer[i], &gEfiPciIoProtocolGuid, (VOID **)&PciIo, ...); } ``` ### 二、Option ROM检测与读取 1. 读取PCI配置空间0x30处的ROM基址寄存器 ```c UINT32 RomAddress; Status = PciIo->Pci.Read( PciIo, EfiPciIoWidthUint32, PCI_ROM_BASE, 1, &RomAddress ); ``` 2. 激活ROM地址空间: ```c // 设置使能位(bit0) if ((RomAddress & PCI_ROM_ADDRESS_ENABLE) == 0) { RomAddress |= PCI_ROM_ADDRESS_ENABLE; PciIo->Pci.Write( PciIo, EfiPciIoWidthUint32, PCI_ROM_BASE, 1, &RomAddress ); } ``` ### 三、内存映射与数据读取 1. 获取ROM大小并分配内存: ```c UINTN RomSize = *(UINT16*)(RomBase + 2) * 512; // 根据规范计算大小 VOID *RomBuffer = AllocatePool(RomSize); PciIo->Mem.Read( PciIo, EfiPciIoWidthUint8, 0, // BarIndex RomBase & ~0xFFFFF, // 地址掩码 RomSize, RomBuffer ); ``` 2. 验证签名: ```c if (*(UINT16*)RomBuffer == PCI_EXPANSION_ROM_HEADER_SIGNATURE) { // 有效ROM[^2] } ``` ### 四、资源释放 ```c FreePool(RomBuffer); RomAddress &= ~PCI_ROM_ADDRESS_ENABLE; PciIo->Pci.Write(...); // 恢复原始寄存器状态 ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luobing4365

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值