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

PCD探究

  • 1 PCD简介
  • 2 如何使用PCD
    • 2.1 PCD的类型
      • FixedAtBuild类型
      • FeatureFlag类型
      • PatchableInModule类型
      • Dynamic类型、DynamicHii类型和DynamicVpd类型
      • DynamicEx类型
    • 2.2 访问PCD变量
    • 2.3 PCD的声明和使用
  • 3 试着写个例子

从《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|0x40000005gEfiMdeModulePkgTokenSpaceGuid.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|0x90000005gRobinPkgPcdSampleGuid.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_CONSUMESgEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintTimes    ## SOMETIMES_CONSUMESgRobinPkgPcdSampleGuid.PcdtouchStrgRobinPkgPcdSampleGuid.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变量,参考HelloWorldif (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型是编译时确定的)
  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下


UEFI开发探索101 – PCD探究相关推荐

  1. UEFI开发探索97 – EDK2模拟器搭建网络环境

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) EDK2模拟器搭建网络环境 1 搭建EDK2开发环境 1)工具安装 2)下载代码库 3)更新子模 ...

  2. UEFI开发探索99 – UEFI Shell下截屏工具

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) UEFI Shell下截屏工具 1 PrintScreenLogger的代码结构 1)Print ...

  3. UEFI开发探索QA – 问题辑录(持续更新)

    最近正在尝试在Unbutu16上搭建开发和调试环境,其中过程一言难尽,到现在也没完成到符合我要求的程度. 正是因为遇到障碍,我今天早上回到Win10+UDK2018的环境下,想重新编译下AppPkg, ...

  4. UEFI开发探索95 – 弹跳小游戏

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) UEFI下的弹跳小游戏 1 Bounce游戏 1.1 游戏架构 1.2 移植和编写代码 1)编写 ...

  5. UEFI开发探索85- YIE002USB开发板(08 制作HID设备)

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) YIE002USB开发板之制作HID设备-编程 1 YIE002-STM32的USB编程 2 调 ...

  6. UEFI开发探索94 – 迷宫小游戏

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) UEFI下的迷宫小游戏 1 Maze程序结构分析 1)定义全局变量 2)设置迷宫 3) 游戏控制 ...

  7. UEFI开发探索02 – 环境搭建1

    (请保留->作者:罗冰 ) 开发初期的目的就是做出可以在pci rom上跑的Oprom,当然是在uefi bios下.我的计划大致如下: 1 搭建完整的编译环境,了解使用哪些库进行编译: 2 我 ...

  8. UEFI开发探索100 – 《UEFI编程实践》发布啦

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) <UEFI编程实践>发布 1 内容简介 第一部分 UEFI环境搭建及UEFI应用构建 ...

  9. UEFI开发探索81- YIE002USB开发板(04 制作HID设备)

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) YIE002USB开发板之制作HID设备-USB系统概述 1 USB规范简介 2 软件工程师眼中 ...

  10. UEFI开发探索74- YIE002USB开发板(03 Windows编程)

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) YIE002USB开发板之Windows编程 1 添加库文件 2 枚举HID设备 2.1 Set ...

最新文章

  1. 不用for 也能实现按照某索引排好某序列
  2. C#调用C++DLL类的方法
  3. iOS - Regex 正则表达式
  4. 海思3559移植yolov3
  5. unittest单元测试框架—基本实例
  6. internal error:failed to get path of 64-bit Program Files directory
  7. php html标签闭合,php截取字符串,完美html自动闭合
  8. XproerIM-V1,2,12,65475发布。
  9. 分享一篇SCCM软件更新的故障排除
  10. Windows核心编程_修改其它进程里的内存值+示例:修改游戏分数
  11. Android SVG矢量图形打造中国地图
  12. 用四叉树加速碰撞检测
  13. 微信公众号小程序怎么做?
  14. 一文读懂Layer 2:Layer 2指基于底层区块链...
  15. 【数据说第九期】如何分析用户行为数据,来驱动业务增长?
  16. 从细胞发现到DNA分子结构的发现,人类经历了三百年
  17. 计算机绘图入门,[2018年最新整理]AutoCAD计算机绘图入门.ppt
  18. python算法与程序基础题库,计算机算法与程序设计(python)MOOC章节测试答案
  19. 2020 icpc 沈阳
  20. 预处理命令 文件包括

热门文章

  1. 英文邮件中常见的英语缩写
  2. 电脑误格式化的数据怎么恢复,误格式化数据恢复教程
  3. 手机企业邮箱客户端哪个好用?
  4. 梁文道:盗版电影网站死去,我们仍然不见光明
  5. Java网络五子棋程序设计论文
  6. ubuntu 安裝deb_Ubuntu离线安装deb包和依赖
  7. 服务器监控报警系统软件设计,Monitor监控报警系统
  8. php语言标记可用什么符号,【单选题】不可用作PHP语言标记用的是什么( )符号 A. ? B. 〈php C. ?...
  9. 【转载】ubuntu16.04 无线/Wifi 上网速度慢的解决方法
  10. python3中的@abstractmethod的用法