UEFI开发探索101 – PCD探究

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



从《UEFI编程实践》出版后,一系列的事情接踵而来,终于在今天,算是告一段落了。

这段时间,有不少机会和业内人员讨论UEFI和BIOS。反思自己对这个领域的理解,深感自己理论的不足。

看过《UEFI编程实践》的网友,应该能了解,书中对于概念的理论部分,阐述得相对较少。我一般都是遵循“提出问题-介绍UEFI相关知识-提供实例”的框架,围绕某一课题进行研究。

其中的一个原因,是因为我本来就是奔着实践为目的,将平时开发中所遇到的课题逐渐展开讨论。另一个原因,是没有深入到EDK2的具体实现去。

因此,从UEFI开发探索第101篇开始,我想逐渐转向对EDK2的代码研究了。如之前研究PCI Option ROM开发一样,这次设立的目标包括:

  1. 对OvmfPkg源代码进行研究,搞清楚固件架构、编译过程、各阶段代码实现等;
  2. 了解Qemu怎么使用固件启动的,以及如何启动操作系统;
  3. 了解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信息:图1 Diskdump中用到的PCD
图1 Diskdump中用到的PCD

观察一下,可以看到许多编程时压根就没注意到的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 *型。

  1. DSC文件中设置PCD变量的值
    可以在DSC文件中设置相应PCD变量的值,比如:
[PcdsFixedAtBuild]
  gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x0f

此设置过程不是必须的,如果没有设置,则使用DEC文件中的默认值。

  1. 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);
}

至此修改完成。编译后在模拟器中运行,结果如下:
图2 Pcdtouch运行结果
图2 Pcdtouch运行结果

在编写过程中,得到的一些经验:

  1. VOID *型(字符串类型)的PCD变量不能只定义为PcdsDynamic型,会编译不通过的。(是因为不能修改吗,具体原因不清楚);
  2. 使用PcdSet32的PCD变量,不能定义为PcdsFixedAtBuild型,编译会提示找不到此PCD变量。(这个倒是很好理解,因为PcdsFixedAtBuild型是编译时确定的)
  3. TokenSpaceGuidCname.PcdCname|DefaultValue|DatumType|Token
    程序中,应该是通过TokenSpaceGuidCname和Token来唯一确定PCD变量的,PcdCname是方便程序员识别的(待确认);
  4. PCD的类型是有优先级,FixedAtBuild>PatchableInModule>Dynamic>DynamicEx,在没有显式声明时按照此顺序认定(待确认);
  5. 示例中没有改DSC文件,需要注意,对于FixedAtBuild,在DEC文件和DSC文件中都是在[PcdsFixedAtBuild]下定义;
    而对于Dynamic类型,DEC中是在[PcdsDynamic]下定义,在DSC中则在[PcdsDynamicDefault](或者PcdsDynamicHii、PcdsDynamicVpd)。名字都是确定的,可查看DSC和DEC文件规范;

后续再继续加深理解。

Gitee地址:https://gitee.com/luobing4365/uefi-exolorer
项目代码位于:/ FF RobinPkg/RobinPkg/Applications/Pcdtouch下


  • 12
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
高通UEFI是高通公司为了支持其处理器和芯片组在Android设备上的引导和初始化而开发的软件模块。UEFI(统一扩展固件界面)是一种新型的固件标准,取代了过去的BIOS(基本输入输出系统)。高通UEFI开发Android开发文档主要提供了有关在使用高通处理器和芯片组的Android设备上进行开发的指导和说明。 该文档首先介绍了如何正确设置开发环境,包括安装必要的软件和驱动程序。接着,文档介绍了高通UEFI的架构和工作原理,包括引导流程、启动流程和初始化过程。开发人员可以深入了解UEFI在Android设备上的作用和功能。 文档还提供了如何进行UEFI开发的具体指导,包括如何编写UEFI应用程序、如何调试和测试UEFI应用程序以及如何进行项目集成等。针对不同的开发需求,文档也提供了各种开发和定制UEFI的选项和工具,使开发人员能够根据实际情况进行灵活的开发和调试。 此外,文档还包含了一些实际案例和示例代码,帮助开发人员更好地理解如何使用高通UEFI开发Android设备,并且提供了一些常见问题和解决方案以供参考。 总而言之,高通UEFI开发Android开发文档为开发人员提供了一个全面的指南,帮助他们理解和应用高通UEFI在Android设备上的开发和定制。通过这些文档,开发人员可以更加有效地利用高通UEFI提供的功能和特性,为Android设备开发出更加稳定和高性能的引导和初始化软件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

luobing4365

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

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

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

打赏作者

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

抵扣说明:

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

余额充值