PCD相关知识

您所在的位置:网站首页 电子产品pcd是什么意思 PCD相关知识

PCD相关知识

2024-06-04 14:56| 来源: 网络整理| 查看: 265

目标: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文件中块名称:PcdsFeatureFlagFeaturePcdPcdsFixedAtBuildFixedPcdPcdsPatchableInModulePatchPcdPcdsDynamicPcdPcdsDynamicExPcdEx

图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 DATABASE mPcdDatabase.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之前)。



【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3