请保留-> 【原文: https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】
从《UEFI编程实践》出版后,一系列的事情接踵而来,终于在今天,算是告一段落了。
这段时间,有不少机会和业内人员讨论UEFI和BIOS。反思自己对这个领域的理解,深感自己理论的不足。
看过《UEFI编程实践》的网友,应该能了解,书中对于概念的理论部分,阐述得相对较少。我一般都是遵循“提出问题-介绍UEFI相关知识-提供实例”的框架,围绕某一课题进行研究。
其中的一个原因,是因为我本来就是奔着实践为目的,将平时开发中所遇到的课题逐渐展开讨论。另一个原因,是没有深入到EDK2的具体实现去。
因此,从UEFI开发探索第101篇开始,我想逐渐转向对EDK2的代码研究了。如之前研究PCI Option ROM开发一样,这次设立的目标包括:
- 对OvmfPkg源代码进行研究,搞清楚固件架构、编译过程、各阶段代码实现等;
- 了解Qemu怎么使用固件启动的,以及如何启动操作系统;
- 了解Ovmf固件如何提供操作系统所需要的各种Table、Runtime Services,甚至SMI Handler等。搞清楚一个UEFI操作系统如何与BIOS结合的。
我了解这是一个不小的目标,会遇到不少的困难,兴趣所在,倒不是特别畏惧。本来的想法,是在树莓派上进行实验,在lab-z(博客:https://www.lab-z.com/)的建议下,觉得OvmfPkg做实验更方便些。
主题确定,后续想到哪里不熟就补足哪里的知识。嗯,先从EDK2全局配置的关键核心-PCD开始研究。
1 PCD简介
Platform Configuration Database(PCD)是EDK2用来进行全局配置的机制,可在代码复用、模块化方面发挥巨大作用。
PCD是把代码里面的可配置选项抽取出来,在platform需要修改的时候,可以不用去修改源代码。其参数的配置,可以在编译过程中、运行时中都可以配置,甚至在二进制文件中也可以配置。
这种设计方式就比较让人着迷,这也使得定制化更为容易,代码更容易维护。
早期我一直以为PCD如同C/C++中的宏,用来提取公用代码,这是错误的。它提供的功能更为广泛,也更复杂。
首先直观地看下平常程序中用到的PCD,以前几篇中的Diskdump工程为例,使用如下命令编译,提取出其所用的PCD信息:
C:\vUDK2018\edk2>build -Y PCD -y pcd.log -p RobinPkg\RobinPkg.dsc -m RobinPkg\Applications\Diskdump\Diskdump.inf -a IA32
输出的信息,存在了pcd.log中。查看下log信息:
观察一下,可以看到许多编程时压根就没注意到的PCD参数。Diskdump使用了MdePkg和ShellPkg中的Protocol,因此也用到了其相关的PCD。
对于PCD的文档,可以参考:
EDK2代码:
MdeModulePkg\Universal\PCD\Dxe\Pcd.inf
https://github.com/tianocore/tianocore.github.io/wiki/EDK-II-Documents:
《EDK II Platform Configuration Database Infrastructure Description》
《EDK II Platform Description(DSC) File Specification》
《EDK II Package Declaration(DEC) File Format Specification》
《EDK II Build Specification》
https://uefi.org/specifications:
《Platform Initialization(PI) Specification》
2 如何使用PCD
PCD可以使用于UEFI存在的大部分时间,除了在SEC阶段、早期的PEI和DXE阶段,基本都可以访问。在使用前,我们需要搞清楚PCD的结构和类型。
2.1 PCD的类型
PCD变量的格式有点像结构体:
TokenSpaceGuidCName.PcdCName
其中,TokenSpaceGuidCName是GUID,而PcdCName是变量名,两者组合构成了一个PCD变量。
PCD有如下的类型。
FixedAtBuild类型
它在编译阶段确定,是静态值,在运行阶段或二进制形态下都不可改。可以认为它就是一个宏了。
FeatureFlag类型
它实际上和FixedAtBuild是同一类型,返回一个Bool类型(True或False),可用于判断条件。
PatchableInModule类型
此类型的变量值在编译的时候确定,它在编译后的二进制文件上使用工具修改。与FixedAtBuild不同,它只能影响一个模块(作用域在一个模块)。
Dynamic类型、DynamicHii类型和DynamicVpd类型
Dynamic类型变量的作用域是整个系统,它是动态的PCD,可以在UEFI运行过程中修改。
DynamicHii类型与Dynamic类型存储的位置不同,Dynamic类型可以认为是存在于Memory中,再加载是会失去原始设置的;而DynamicHii类型是存在Efi variable中的(NVRAM中),其修改时非易失性的。
而DynamicVpd类型变量是只读的,不可写的,一般出厂确定。
DynamicEx类型
与Dynamic类型类似,相当于加强版。其与Dynamic类型的区别,在于是否使用二进制文件中的PCD。比如FSP,如果要使用其中的PCD变量,则FSP中的PCD类型必须设置为### DynamicEx类型。
2.2 访问PCD变量
为管理PCD变量,PEI提供了PCD_PPI和EFI_PEI_PCD_PPI;DXE提供了PCD_PROTOCOL和EFI_PCD_PROTOCOL。
不过,为了方便使用,EDK2中引入了PCD Library,把这些访问细节隐藏了起来。(MdePkg\Include\Library\PcdLib.h)
库中包含如下函数:
PcdGetXX()
PcdSetXX()
PcdGetExXX()
PcdSetExXX()
PcdToken()
PCDSetSku()
PcdGetNextToken()
PcdGetNextTokenSpace()
CallBackOnSet()
CancelCallBack()
其中,XX可以为8、16、32、Size、Ptr或者Boolean。
2.3 PCD的声明和使用
PCD的使用,基本可以按照如下流程进行。
1 DEC文件中声明PCD变量的基本信息,
比如:
[PcdsFixedAtBuild, PcdsPatchableInModule, PcdsDynamic, PcdsDynamicEx]
gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintTimes|1|UINT32|0x40000005
gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintString|L"UEFI Hello World!\n"|VOID*|0x40000004
其格式为:
TokenSpaceGuidCname.PcdCname|DefaultValue|DatumType|Token
如前所述,PcdCname为变量名,DefaultValue为其默认值,DatumType是PCD的数据类型,Token是一个32位的整型,在DEC中每个PCD都有一个独有的Token。
DatumType可以是BOOLEAN、UINT8、UINT16、UINT32、UINT64或VOID *型。
2 DSC文件中设置PCD变量的值
可以在DSC文件中设置相应PCD变量的值,比如:
[PcdsFixedAtBuild]
gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x0f
此设置过程不是必须的,如果没有设置,则使用DEC文件中的默认值。
3 INF文件中声明
在模块的INF文件中,需要声明PCD变量,才可以在源码中使用。比如:
[Pcd]
gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintString
gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintTimes
只需要列出PCD变量名就可以了,其他信息不用列出。
完成上述工作后,就可以在源代码中,使用PCD库函数访问PCD变量了。示例如下:(摘自MdeModulePkg\Application\HelloWorld\HelloWorld.c)
if (FeaturePcdGet (PcdHelloWorldPrintEnable)) {
for (Index = 0; Index < PcdGet32 (PcdHelloWorldPrintTimes); Index ++) {
//
// Use UefiLib Print API to print string to UEFI console
//
Print ((CHAR16*)PcdGetPtr (PcdHelloWorldPrintString));
}
}
3 试着写个例子
在UEFI应用开发或者Option ROM开发中,基本上不用PCD变量。但并不妨碍在UEFI应用上使用它们,我们试着在RobinPkg的某个Application上,来使用PCD变量。
我选择之前开发的Diskdump工程,改名为Pcdtouch,尝试使用PCD变量。当然,随便选一个其他的工程也可以,刚好这个工程就在眼前,就随手在它上面改造了。
修改步骤如下:
1. 修改RobinPkg.dec
添加如下语句:
[Guids]
gRobinPkgPcdSampleGuid = { 0xe7e1efa6, 0x7607, 0x3a78, { 0xc7, 0xdd, 0x43, 0xe4, 0xbd, 0x72, 0xc1, 0x19 }}
# [PcdsFixedAtBuild, PcdsPatchableInModule, PcdsDynamic, PcdsDynamicEx]
[PcdsPatchableInModule, PcdsDynamic, PcdsDynamicEx]
gRobinPkgPcdSampleGuid.PcdtouchValue|12345|UINT32|0x90000005
gRobinPkgPcdSampleGuid.PcdtouchStr|L"Hello,UEFI World, this is robin!\n"|VOID*|0x90000004
2. 修改Pcdtouch.inf
DSC文件中可以修改PCD变量的值,这里我们不需要修改,不用去改DSC文件。
直接修改INF文件就可以了,添加将要在源程序中用到的PCD变量:
[FeaturePcd]
gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintEnable ## CONSUMES
[Pcd]
gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintString ## SOMETIMES_CONSUMES
gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintTimes ## SOMETIMES_CONSUMES
gRobinPkgPcdSampleGuid.PcdtouchStr
gRobinPkgPcdSampleGuid.PcdtouchValue
除了在DEC文件中添加的两个PCD变量外,还把MdeModulePkg中的几个PCD变量也声明了,待会在程序中要用。
3. 在源程序Pcdtouch.c中添加代码
主要是修改main程序:
int
main (
IN int Argc,
IN char **Argv
)
{
UINT32 Index,myValue;
Index = 0;
myValue = 0;
// 测试MdeModulePkg中的PCD变量,参考HelloWorld
if (FeaturePcdGet (PcdHelloWorldPrintEnable)) {
for (Index = 0; Index < PcdGet32 (PcdHelloWorldPrintTimes); Index ++) {
Print ((CHAR16*)PcdGetPtr (PcdHelloWorldPrintString));
}
}
Print ((CHAR16*)PcdGetPtr (PcdtouchStr)); //打印PCD变量
Print(L"\n");
myValue = PcdGet32(PcdtouchValue);
Print(L"PcdtouchValue = %d\n", myValue);
PcdSet32(PcdtouchValue,321);
// LibPcdSet32(PcdtouchValue,321);
myValue = PcdGet32(PcdtouchValue);
Print(L"now,PcdtouchValue = %d\n", myValue);
}
至此修改完成。编译后在模拟器中运行,结果如下:
在编写过程中,得到的一些经验:
- VOID *型(字符串类型)的PCD变量不能只定义为PcdsDynamic型,会编译不通过的。(是因为不能修改吗,具体原因不清楚);
- 使用PcdSet32的PCD变量,不能定义为PcdsFixedAtBuild型,编译会提示找不到此PCD变量。(这个倒是很好理解,因为PcdsFixedAtBuild型是编译时确定的)
后续再继续加深理解。
Gitee地址:https://gitee.com/luobing4365/uefi-exolorer
项目代码位于:/ FF RobinPkg/RobinPkg/Applications/Pcdtouch下
13,351 total views, 15 views today
UDK2018可在哪款主板上跑
不明白你的意思,UDK2018是EDKII的一个版本而已,用来编译UEFI程序的
此设置过程不是必须的,如果没有设置,则使用DEC文件中的默认值。
請問一下, 您提到的這段, 假設DSC沒有設置, DEC 內容如下
[PcdsFixedAtBuild, PcdsPatchableInModule, PcdsDynamic, PcdsDynamicEx]
gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintTimes|1|UINT32|0x40000005
TokenSpaceGuidCname.PcdCname|DefaultValue|DatumType|Token
Value 的話會是 1 , 那麼類型的話呢?
DEC中给出一个缺省值,可在DSC中修改,主要是表明这个含义。类型就是DEC中给出的
你好,NT32 模拟器还能用么?我重新Git了2017代码编译后运行NT32模拟器,可以进去,但是进去就卡在了Shell,鼠标键盘皆不可用。不知大佬是否有遇到此类情况
嗯,没有试过,也许会有问题。后面的模拟器都用EmulatorPkg了,注意看下它们里面的说明。我遇到过的是EmluatorPkg使用VS2019编译,模拟器不接受鼠标键盘消息。本来怀疑是公司内部监控软件的副作用,没有去深究其原因。现在转到Linux下编译了
你好,我重新Git了edk2017的代码,编译后运行NT32模拟器,可以进去,但是还是卡在了Shell处,键盘鼠标皆不可用。不知你是否有遇到过此类情况。