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宏来修改值;DynamicDynamicEx是系统级的、动态的,在整个boot过程中可以被修改,同时在整个系统中都能生效。DynamicEx类的PCD主要用在二进制文件中使用到PCD的情况,可以实现跨平台访问和修改以二进制发布的驱动中的PCD的值,只知道DynamicExDynamic的加强版,如果在代码中没有二进制形式,或者二进制文件没有用到PCD,那么这里使用的PCD 类型就是Dynamic的,但是如果是用binary方式集成进来了,要使用二进制中的PCD就必须要用DynamicEx类型的PCD,用PcdGetEx/PcdSetEx来访问变量;需要注意的是上面的类型并不是在一个SPEC中定义的,前面的4中是满足EDKII规范,而最后一个满足的是PI规范。

        Dynamic 有三个子类DynamicHIIDynamicVpdDynamicExDynamicHII是放在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文件中块名称:
PcdsFeatureFlagFeaturePcd
PcdsFixedAtBuildFixedPcd
PcdsPatchableInModulePatchPcd
PcdsDynamicPcd
PcdsDynamicExPcdEx

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值