本文主要是介绍PCD相关知识,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目标:PCD的工作原理,以及在编译过程中对PCD的处理流程。
输出:PCD的学习笔记
1:UEFI中PCD基础知识
要明白PCD的工作原理,首先要对PCD有一个全面的了解,接下来讲解PCD相关内容,如有不对的地方,请及时指正,谢谢!!!
1.1 PCD的定义和作用:PCD(Platform Configuration Database),是一个UEFI下可访问的数据库,EDK2用来进行全局配置,PCD是把代码里面的可配置选项抽取出来,platform需要修改的时候,可以不用去修改源代码。其参数的配置,可以在编译过程中、运行时中都可以进行,甚至在二进制文件中也可以配置。降低了代码维护的工作量,增加了可复用性。
使用时机:PCD可以使用于UEFI存在的大部分时间,除了在SEC阶段、早期的PEI和DXE阶段,基本都可以访问。在使用前,我们需要搞清楚PCD的结构和类型。
1.2 PCD变量的格式:定义如下代码所示
TokenSpaceGuidCname.PcdCname|DefaultValue|DatumType|Token
PcdCname为变量名,DefaultValue为其默认值,DatumType是PCD的数据类型,Token是一个32位的整型,在DEC中每个PCD都有一个独有的Token。DatumType可以是BOOLEAN、UINT8、UINT16、UINT32、UINT64或VOID *型。使用流程见1.6
1.3 PCD变量的类型:PCD类型和描述如图1、按照模块级别和Boot阶段是否可修改分类如图2所示。
图1 PCD类型和描述
分类 | 是否可修改 | 备注 |
FixedAtBuild类型 | 在运行阶段或二进制形态下都不可改。 | 编译阶段确定,是静态值 |
FeatureFlag类型 | 不可改 | 它实际上和FixedAtBuild是同一类型,它的值只能是Boolean的TRUE或者FALSE |
PatchableInModule类型 | 在编译的时候确定,它在编译后的二进制文件上使用工具修改。 | 与FixedAtBuild不同,它只能影响一个模块(作用域在一个模块)。 |
Dynamic类型 | 可以在UEFI运行过程中修改。 | 作用域是整个系统,Dynamic类型可以认为是存在于Memory中,再加载是会失去原始设置的 |
DynamicHii类型 | 存在Efi variable中的(NVRAM中) | 其修改时非易失性的。 |
DynamicVpd类型 | 只读的,不可写的 | 一般出厂确定。 |
DynamicEx类型 | 可修改 | 与Dynamic类型类似,相当于加强版,其与Dynamic类型的区别,在于是否使用二进制文件中的PCD |
图2 按照模块级别和Boot阶段是否可修改分类
如图2所示,所谓模块级别就是在不同的模块中可以有不同的值。在Boot阶段不可修改类似于宏定义或者用const修饰的变量;
PatchableInModule类型的PCD值在Boot阶段可以修改,可以用于以二进制形式发布的模块中,PatchableInModule PCD在编译完成后的二进制文件上是可以用特殊的办法更的;
Dynamic :动态的PCD它的特点是可以在UEFI运行的过程中通过Set宏来修改值;Dynamic和DynamicEx是系统级的、动态的,在整个boot过程中可以被修改,同时在整个系统中都能生效。DynamicEx类的PCD主要用在二进制文件中使用到PCD的情况,可以实现跨平台访问和修改以二进制发布的驱动中的PCD的值,只知道DynamicEx是Dynamic的加强版,如果在代码中没有二进制形式,或者二进制文件没有用到PCD,那么这里使用的PCD 类型就是Dynamic的,但是如果是用binary方式集成进来了,要使用二进制中的PCD就必须要用DynamicEx类型的PCD,用PcdGetEx/PcdSetEx来访问变量;需要注意的是上面的类型并不是在一个SPEC中定义的,前面的4中是满足EDKII规范,而最后一个满足的是PI规范。
Dynamic 有三个子类DynamicHII
、DynamicVpd
、DynamicEx,
DynamicHII是放在EFI variable(NVRAM中)的,修改是非易失性的,就是set了以后下次加载还是这次set的值。DynamicVpd ReadOnly,不可写。存放系统的default section,厂商的出厂设置就放在这里,是必不会也不可被修改的。
PCD根据存储方式分类:
a)默认存储:PcdsDynamicDefault和PcdsDynamicExDefault
b)变量存储:PcdsDynamicHii和PcdsDynamicExHii
c)OEM指定的存储区域:PcdsDynamicVpd和PcdsDynamicExVpd
1.4 PCD的数据类型:PCD的数据类型有BOOLEAN、UINT8、UINT16、UINT32、UINT64或VOID *型。C结构体或者数组
1.5 PCD使用周期:在诸如SEC阶段,以及PEI、DXE阶段的早期,某些PCD相关的模块还没有加载起来之前,这些类型的PCD不能访问。
1.6 PCD使用流程:PCD在Dec文件中声名、INF文件引用、Dsc文件中修改、在C中修改和访问。使用PCD的流程如图3所示。步骤如下所示。
图3 使用流程
1.6.1 PCD在Dec文件中声名如图4所示。
图4 Dec文件中声名
1:PcdTokenSpaceGuidName可以使用Dec文件中现有的,也可以自己声明一个。在 [Guids] 块下声明,大括号中的内容是唯一不重复的Guid;
2:PCD 由 :TokenSpaceGuid 和 TokenNumber (就是上面标红的)唯一确定,与TokenName无关;
3:TokenName (与PcdTokenSpaceGuidName一起)在获取PCD的值的时候使用;
4:图中的[Pcds...] 指出了该PCD支持的类型,可以用FixedAtBuild,FeatureFlag,PatchableInModule,Dynamic 以及 DynamicEx 中的任意一种代替,也可以是除了 FeatureFlag 之外的多个组合在一起并使用 ’,’ 进行分隔;
5:DatumType 是PCD数据类型,在上述1.4小节已经讲述过。
6:声明后,只有Value是可以在DSC中更改的,其他的都不可以;
7:PcdTokenSpaceGuidName 类似于C++ 中的命名空间,在不同的 PcdTokenSpaceGuidName 下,PcdTokenName 可以相同。
1.6.2 INF文件引用:
PCD类型: | INF文件中块名称: |
PcdsFeatureFlag | FeaturePcd |
PcdsFixedAtBuild | FixedPcd |
PcdsPatchableInModule | PatchPcd |
PcdsDynamic | Pcd |
PcdsDynamicEx | PcdEx |
图5 INF文件引用
只需要列出PCD变量名就可以了,其他信息不用列出。
注意:不同类型的 PCD 在 文件中对应的块名称不同,使用多种类型的 PCD 要分别在 INF 文件中对应的块中引用。不同类型的 PCD 如图6所示。
图6 不同类型的 PCD
例如在MdeModulepkg/Application/HelloWorld/HelloWorld.inf中为Pcd块,如图7所示。
图7 Pcd块
此时可以设置为PcdsDynamicDefault等类型
用DEBUG ((EFI_D_ERROR, "[beni]Version: 0x%x.\n", PcdGet32(PcdOemVersion)));要使用PcdGet32这种的宏,需要包含PcdLib这个库。还需要在inf中包含该PCD。
注意: 如果一个PCD被声明多种类型且在INF文件中引用时都放 [Pcd] 块中,编译工具会根据优先级决定PCD的类型:PcdsFixedAtBuild > PcdsPatchableInModule > PcdsDynamicDefault > PcdsDynamicExDefault。
1.6.3 Dsc文件中修改。如图8所示。例子EmulatorPkg/EmulatorPkg.dsc如图9所示
图8 Dsc文件中修改
图9 例子
在 DSC 文件中对 PCD做初始化,如果没有初始化则使用 DEC 文件中默认的 PCD 值。
1.6.4 在C中修改和访问。 PcdLib 提供了接口用于 PC的 的读取和修改。PcdGetXX() 和 PcdSetXX() 可以用于任何 PCD 类型(XX:8、16、32、Size、Ptr、Boolean)。PcdGetXX() 用于根据 PCD Name 获取 PCD 值,就是 TokenNumber;PcdSetXX() 用于根据 PCD 的名称重新设置 PCD 的。
注意:
2:PCD工作原理总结
根据1中关于PCD的介绍,现在总结一下,
在UEFI (统一的可扩展固件接口)中,PCD (平台配置数据)是一种机制,用于管理和访问系统固件中的配置信息。PCD包含了多个键值对,用于记录各种硬件和软件的配置数据。
PCD的工作原理如下:
1. 定义PCD:在系统固件中,通过编写配置文件,定义所需的PCD。配置文件中包含每个PCD的标识符、类型、默认值等信息。(Dec中)
2. 加载PCD:在系统启动过程中,固件读取配置文件,并加载PCD到内存中。加载时,固件会根据配置文件的定义,为每个PCD分配内存空间,并将默认值写入。
3. 访问PCD:在运行时,操作系统和驱动程序可以通过固件提供的接口来访问PCD。接口可以提供读取和写入PCD的功能。(上述的PcdGetXX() 或者PcdSetXX() )
4. 修改PCD:操作系统和驱动程序可以通过接口修改PCD的值。这样可以动态地调整配置信息,以满足系统需求。(Dsc中)
5. 运用PCD:操作系统和驱动程序可以使用PCD中保存的配置信息来进行各种操作。例如,根据PCD中的硬件配置信息,决定设备的初始化方式。
3:编译过程中对PCD的处理流程总结
此部分讲解主要针对四个例子,分别是FixedAtBuild PCD、FeatureFlag PCD、Dynamic PCD、PatchableInModule PCD。
3.1:FixedAtBuild PCD,声名如图10、使用这个FixedAtBuild PCD,在源文件中DEBUG打印,如图11,再查看该模块编译目录中的AutoGen.c文件如图12,AutoGen.h分析如图13。
图10 声名
图11 DEBUG打印
图12 AutoGen.c
图13 AutoGen.h
如图11,我们使用的是PcdGet8 (PcdTestVar1));将它展开后的结果就是_PCD_GET_MODE_8_PcdTestVar1,它在上述的如图13的AutoGen.h就已经定义了,最终的值就是0xA5(图10中的)。也就是说,对应FixedAtBuild PCD来说,它就是在编译的时候通过宏的方式生成的。也因此它是固定不变的一个值。
3.2 FeatureFlag PCD:前面已经讲述了这个,这个相当于类型是BOOELAN的FixedAtBuild PCD。注意访问这个变量可以使用两种方式,如下所示。
#define FeaturePcdGet(TokenName) _PCD_GET_MODE_BOOL_##TokenName
#define PcdGetBool(TokenName) _PCD_GET_MODE_BOOL_##TokenName
3.3 Dynamic PCD:还是和3.1的分析流程一样,先创建PCD->DEBUG使用->编译后查看AutoGen.c->再看AutoGen.h文件,依次分析。创建文件如图14,使用如图15,查看AutoGen.c(无相关内容),AutoGen.h如图16。
图14
图15
图16
从图16中可以看出这个明显和3.1小节的图13明显不同,3.1小节的图13里面都是固定值,但是图16里面宏定义的都是相关库函数,如LibPcdGet32、LibPcdGetSize、LibPcdSet32、LibPcdSet32S。我们以LibPcdGet32为例进行分析,LibPcdGet32实现如下图17所示。
图17
可以看到此时函数中出现Protocol字样,我们想到这个可能和Protocol产生关系。其实不同阶段依赖对象不同,DXE阶段是Protocol,而PEI阶段是PPI。Protocol在Pcd.inf模块中安装。Pcd.inf模块分为PEI和DXE两个版本,分别放在PEI阶段和DXE阶段的最前面,只有整个模块初始化完成之后,才能够开始正常使用PCD宏来访问Dynamic PCD变量。以DXE阶段的Pcd.inf模块为例,它主要做了两件事情:
1. 初始化该阶段使用的PCD数据库;
2. 安装各种处理PCD需要的Protocol;
此时有两个问题出现了:PCD数据库怎么建立的?处理PCD的Protocol有哪些?先看第一个问题,我们从源码中分析,
****************BuildPcdDxeDataBase()****************// Assign PCD Entries with default value to PCD DATABASEmPcdDatabase.DxeDb = LocateExPcdBinary ();ASSERT(mPcdDatabase.DxeDb != NULL);PcdDxeDbLen = mPcdDatabase.DxeDb->Length + mPcdDatabase.DxeDb->UninitDataBaseSize;PcdDxeDb = AllocateZeroPool (PcdDxeDbLen);ASSERT (PcdDxeDb != NULL);CopyMem (PcdDxeDb, mPcdDatabase.DxeDb, mPcdDatabase.DxeDb->Length);FreePool (mPcdDatabase.DxeDb);mPcdDatabase.DxeDb = PcdDxeDb;
反正肯定是从哪里拿了一坨数据,然后分配点空间,这不就成了。拿的一坨数据肯定是从LocateExPcdBinary ()搞的,分配空间肯定是AllocateZeroPool (PcdDxeDbLen),这PCD数据库不久成了。但是重点是LocateExPcdBinary ()是从哪里搞的一坨数据?那只能看源码了,LocateExPcdBinary ()里面部分内容如下。
Status = GetSectionFromFfs (EFI_SECTION_RAW,0,(VOID **) &DxePcdDbBinary,&DxePcdDbSize);ASSERT_EFI_ERROR (Status);
网上说PCD数据是存放在当前FFS的第一个Section开始的数据。FFS是啥我还不清楚,但是看图知道最后一个参数是大小,倒数第二个应该是传入的地址。PCD数据的查看需要参考(编译生成的二进制的结构相关内容)。这里我们只需知道,Dynamic PCD的数据是在编译的时候初始化并存放在UEFI二进制中的,然后在UEFI运行过程中会获取这些数据,并存放到内存中,后续就可以修改了。这是第一个问题的答案。
接着解决第二个问题:安装的Protocol如下:
typedef struct {PCD_PROTOCOL_SET_SKU SetSku;PCD_PROTOCOL_GET8 Get8;PCD_PROTOCOL_GET16 Get16;PCD_PROTOCOL_GET32 Get32;PCD_PROTOCOL_GET64 Get64;PCD_PROTOCOL_GET_POINTER GetPtr;PCD_PROTOCOL_GET_BOOLEAN GetBool;PCD_PROTOCOL_GET_SIZE GetSize;PCD_PROTOCOL_GET_EX_8 Get8Ex;PCD_PROTOCOL_GET_EX_16 Get16Ex;PCD_PROTOCOL_GET_EX_32 Get32Ex;PCD_PROTOCOL_GET_EX_64 Get64Ex;PCD_PROTOCOL_GET_EX_POINTER GetPtrEx;PCD_PROTOCOL_GET_EX_BOOLEAN GetBoolEx;PCD_PROTOCOL_GET_EX_SIZE GetSizeEx;PCD_PROTOCOL_SET8 Set8;PCD_PROTOCOL_SET16 Set16;PCD_PROTOCOL_SET32 Set32;PCD_PROTOCOL_SET64 Set64;PCD_PROTOCOL_SET_POINTER SetPtr;PCD_PROTOCOL_SET_BOOLEAN SetBool;PCD_PROTOCOL_SET_EX_8 Set8Ex;PCD_PROTOCOL_SET_EX_16 Set16Ex;PCD_PROTOCOL_SET_EX_32 Set32Ex;PCD_PROTOCOL_SET_EX_64 Set64Ex;PCD_PROTOCOL_SET_EX_POINTER SetPtrEx;PCD_PROTOCOL_SET_EX_BOOLEAN SetBoolEx;PCD_PROTOCOL_CALLBACK_ONSET CallbackOnSet;PCD_PROTOCOL_CANCEL_CALLBACK CancelCallback;PCD_PROTOCOL_GET_NEXT_TOKEN GetNextToken;PCD_PROTOCOL_GET_NEXT_TOKENSPACE GetNextTokenSpace;
} PCD_PROTOCOL;
这里就可以看到上文中LibPcdGet32()里面调用第四行的Get32()函数。观察发现上述代码中最多的就是Getxxx和Setxxx,我们进一步看这些种类库函数的实现,Getxxx这类库函数实现过程中,最重要的代码为
VOID *
GetWorker (IN UINTN TokenNumber,IN UINTN GetSize)
Setxxx这类库函数实现过程中,最重要的代码为
EFI_STATUS
SetWorker (IN UINTN TokenNumber,IN VOID *Data,IN OUT UINTN *Size,IN BOOLEAN PtrType)
GetWorker()的作用就是在PCD数据库里面找到对应的PCD的指针,而SetWork()的作用就是找到指针然后赋值。
3.4 PatchableInModule PCD:DEC声名、模块中使用、查看AutoGen.c、查看AutoGen.h、分别如图18、图19、图20、图21所示。
图18
图19
图20
图21
由上述四个图所示,与之前不同的是,这个是由volatile修饰的(用这个修饰的变量是容易发生改变的,所以读取的时候一定要从原始的寄存器中读取,不能用编译器中缓冲的数据代替。具体的用法查询volatile。)理论上可以通过工具(GenPatchPcdTable.exe和PatchPcdValue.exe)来修改这个值(在生成最终的OVMF.fd之前)。
这篇关于PCD相关知识的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!