今天看UiAPP下的入口函数InitializeUserInterface()
时看见一个PcdGet32,不明白是干什么的,VSCode上显示的是本函数相关的信息(显然不对 ),后面搜索不到相关的源码。之后就去补充了相关的知识,记录一下,算是一个比较简单浅显的版本。
UEFI——PCD研究
1 PCD OVERVIEW
PCD(Platform Configuration Database),是一个UEFI下可访问的数据库,EDK2用来进行全局配置,对代码复用,模块化开发有巨大的帮助。
具体来说就是,代码中进行平台配置相关的东西,platform需要改的时候不用去修改代码了。在编译的时候,运行的时候,甚至是生成了二进制的时候都可以配置、修改。更容易有针对性的设计,对代码的维护也会方便很多。(有点类似于.uni和.Vfr的机制)
虽然在EDK2源码中他涉及到的宏很多,但是这种方式确实是有别于C/C++的宏,并不是简单的公用代码的提取,或者说完全不是一个东西。
PCD变量格式
TokenSpaceGuidCName.PcdCName
其中TokenSpaceGuidCName是一个GUID,而PcdCName是这个PCD变量的名字,这两个加在一起构成了一个PCD变量。
2 PCD USAGE
2.1 PCD TYPES
PCD可以分为两类:
1)有三个分别是:FixedAtBuild PCD
, FeatureFlag PCD
,PatchableInModule PCD
, 其中 FixedAtBuild PCD 和 FeatureFlag PCD 在编译的时候起作用的,静态的。类似于C中的静态全局变量,但是后面不能修改。在定义的时候就赋值了,之后是不能用代码修改的,只能是在.dec/.dsc中修改。
PatchableInModule PCD在编译完成后的二进制文件上是可以用特殊的办法更改的。后面详细介绍。
2)Dynamic/DynamicEx PCD 等其它的 可以称为动态的,在UEFI运行的时候可以get,还可以用set宏修改,作用域是system级,在代码中传递和使用。(比前面的复杂一些)
FixedAtBuild
编译阶段确定的值,整个UEFI运行阶段不可改的静态值,二进制下也不可以改,可以认为是一个宏,const全局变量。是一个module level,并非系统级别的。即是说不同模块可以配置不同PCD的值。
FeatureFlag
也是编译时确定的静态设置,看名字中有个Flag也能猜出来这是个Boolean类型的,他和FixedAtBuild最大的区别就是他返回值就是一个TURE或者FALSE。用于判断条件中,也是一个module level的。
PatchableInModule
在编译时确定值,和FixedAtBuild一样也是module level,只能影响一个模块,不会影响其他的模块,相当于一个inf 的 source范围。不同是他在编译后的二进制文件中可以用工具修改,并且他不是const 的全局变量。
Dynamic/DynamicEx
动态值,在整个UEFI运行的过程中,有人会get,有人会set,是一直可以改变的值,system level,作用域是整个system。整个BIOS代码每个模块访问这个Dynamic PCD 都是共享同一个值,如果某个模块修改了他的值,那么其他模块访问的就是修改以后的值了。通过set宏来改变。Dynamic 有三个子类DynamicHII
、DynamicVpd
、DynamicEx
。区别目前还不是特别理解,只知道DynamicEx是Dynamic的加强版,如果在代码中没有二进制形式,或者二进制文件没有用到PCD,那么这里使用的PCD 类型就是Dynamic的,但是如果是用binary方式集成进来了,要使用二进制中的PCD就必须要用DynamicEx类型的PCD。
DynamicHII/DynamicVpd
DynamicHII与Dynamic不同,后者是放在memory中的,也就是说修改了再加载的话会丢失上一次的值,获取到最新define的值。而DynamicHII是放在EFI variable(NVRAM中)的,修改是非易失性的,就是set了以后下次加载还是这次set的值。
DynamicVpd ReadOnly,不可写。存放系统的default section,厂商的出厂设置就放在这里,是必不会也不可被修改的。
2.2 EXAMPLE
原本计划介绍 FixedAtBuild 类型的,但是我看到的代码中的PcdSetupVideoHorizontalResolution
是PatchableInModule类型的,这两个类型的访问方式没什么不同,后者只是可以在二进制文件生成之后修改,而前者不可以。
PcdSetupVideoHorizontalResolution
在源码中的位置:
可以看到也是有这个PcdGet32宏,我也是因为没找到这个的定义才进行相关知识的学习。
- 在.dec文件中声明这个PCD变量:
源码:
[PcdsPatchableInModule, PcdsDynamic, PcdsDynamicEx]
gEfiMdeModulePkgTokenSpaceGuid.PcdSetupVideoHorizontalResolution|800|UINT32|0x4000000b
介绍一下声明的格式:
他这里有两句,第一句[PcdsPatchableInModule, PcdsDynamic, PcdsDynamicEx],声明了这个PCD的类型,就前面介绍的PCD类型。
第二句的规则是这样的:
#Comment
PcdTokenSpaceGuidName.PcdTokenName|Value[|DatumType[|MaxSize]]|Token
PcdTokenSpaceGuidName.PcdTokenName
前面已经介绍过了,不可以重复,也不可以轻易改变,这两个共同构成一个PCD变量。Value
也叫做DefaultValue
就是这个PCD变量的默认值,如果你在后面的.dsc文件中没有修改他的值,他就是用这个默认值。DatumType
是这个PCD变量的数据类型有UINT 8 16 32 等等,如果是VOID * 的话还要在MaxSize部分写上这个Buffer的最大值,比如128、256等等。Token
是UINT 32类型的,每个.dec文件中的PCD都有独一无二的Token。
他是符合BNF的,#后面的是注释,[ ]内的是可选项,没有应该也可以。
- 在.dsc中可以修改
修改的规则:
[Pcds...]
PcdTokenSpaceGuidName.PcdTokenName|Value[|DatumType[|MaximumDatumSize]]
这些字段的值和前面的一样,[ ]内的也是可选的,不是必须包括的。一般的写法就是PcdTokenSpaceGuidName.PcdTokenName|Value
,像图片里面的那样。
将原本的800改成了640,当然这还不是同一个文件下的dsc文件,从侧面也证实了这确实是一个全局变量。
PCD可以在.dsc文件中进行修改,包括PCD的值和类型,假如PCD在.dec文件中进行了定义,在inf文件中使用,但在.dsc中并没有重新set,这时候PCD就会使用默认值(.dec中的Value),类型则是支持的最初的类型,类型也是有一个优先级,优先考虑FixAtBuild,其次PatchInModule,然后Dynamic,DynamicEx。(这部分不确定)
3.在.inf文件中引用
[Pcd]
PcdTokenSpaceGuidName.PcdTokenName|[Value]
一般的写法就是列出这个变量的名字,值是不管的。
3 AutoGen
AutoGen.h
这里可以看见 在AutoGen.h 中 将 _PCD_PATCHABLE_VALUE_PcdSetupVideoHorizontalResolution
设置成了800u
AutoGen.c
然后最精彩的操作在于,他通过一些的#define 将_PCD_PATCHABLE_VALUE_PcdSetupVideoHorizontalResolution
这个变量改成PcdSetupVideoHorizontalResolution
加上前缀_PCD_GET_MODE_32_
,而后面的PcdGet32()
中直接用##拼接这个变量名就可以直接得到他的值,所以也算知道了PcdGet32()
这个函数的来龙去脉了。
4 PCD LIBRARY
为了方便代码的编写。引入了PCDLib,这里面有常用的接口可以使用,我之前看到的PcdGet32()
就在其中。
这里面有许多好用的函数,比如
PcdGetXX()
PcdSetXX()
PcdGetExXX()
PcdSetEx()
PcdToken()
PcdSetSku()
PcdGetNextToken()
PcdGetNextTokenSpace()
CallBackOnSet()
CancelCallBack()
其中“XX" = 8 16 32 Size Ptr Boolean
而我们最常使用的就是PcdGetXX()
和 PcdSetXX()
最后:动态的PCD机制还不太了解,而修改二进制文件中的PCD还需要去实践。
参考博客: