4

UEFI开发探索101 – PCD探究

 2 years ago
source link: http://yiiyee.cn/blog/2021/10/18/uefi%E5%BC%80%E5%8F%91%E6%8E%A2%E7%B4%A2101-pcd%E6%8E%A2%E7%A9%B6/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

UEFI开发探索101 – PCD探究

请保留-> 【原文:  https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】

从《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

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

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

图2 Pcdtouch运行结果

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

  1. VOID *型(字符串类型)的PCD变量不能只定义为PcdsDynamic型,会编译不通过的。(是因为不能修改吗,具体原因不清楚);
  2. 使用PcdSet32的PCD变量,不能定义为PcdsFixedAtBuild型,编译会提示找不到此PCD变量。(这个倒是很好理解,因为PcdsFixedAtBuild型是编译时确定的)

后续再继续加深理解。

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

1,112 total views, 8 views today


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK