2013年12月26日 星期四

PCD (Platform Configuration Database) - Part 1

EDK II相較於EDK來說,除了更模組化的設計(少了許多沒必要且重複的Library...),最大的不同就是多了PCD這個新功能!

依照PCD類型的不同,我們的Module可以讀取在Compiler前就已設定好的Macro,或者是使用Dynamic PCD來讓各個Module相互存取數值。

在EDK時BIOS內的不同Module要互相傳遞數值,最常使用的可能是CMOS、Memory或Variable。
但CMOS可使用的位置並不算多(雖然還算滿多的...),也要顧慮到該位址是否已被占用。
而Memory也要先配置好空間,然後別支Module也要知道該位置,說真的也有點麻煩...
再來就是Variable,Variable說方便是方便,但使用時都要做一些繁瑣動作,例如訂好Name、GUID還有Data Type(雖然PCD也要訂定好GUID與各資料型別等部分,但Compiler會做掉很多地方),對於一般不需保存於NVS的資料,個人比較偏好使用PCD,但其生命周期只有Boot-time而已。

PCD分為五種型態:
  • PcdsFeatureFlag
  • PcdsFixedAtBuild
  • PcdsPatchableInModule
  • PcdsDynamic
  • PcdsDynamicEx
某些PCD在編譯時,就會被宣告為使用它的Module裡的常數,所以只能去讀取它原先就定好的值。
其它PCD則是可以在Boot-time時期做存取的動作。

可在Package中的DEC檔中宣告每個不同的PCD。
MdePkg.dec為例:
[PcdsFixedAtBuild, PcdsPatchableInModule, PcdsDynamic, PcdsDynamicEx]  gEfiMdePkgTokenSpaceGuid.PcdPciExpressBaseAddress|0xE0000000|UINT64|0x0000000a  gEfiMdePkgTokenSpaceGuid.PcdUefiVariableDefaultLangCodes|"engfraengfra"|VOID*|0x0000001c  gEfiMdePkgTokenSpaceGuid.PcdUefiVariableDefaultLang|"eng"|VOID*|0x0000001d

在DEC中的PCD宣告,可以有多種型態,以上面的例子來說,這些PCDs就宣告了四種型態,並且分別給予它們初始值資料型別Token值

然後可在DSC中定義它實際的PCD型態初始值
MdePkg.dsc為例:
[PcdsFixedAtBuild]
  gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x0f
  gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000000
  gEfiMdePkgTokenSpaceGuid.PcdPciExpressBaseAddress|0xE0000000

========================================
那如果在DSC定義一個與DEC中所宣告的型態不同會如何?
MdePkg.dec
[PcdsFixedAtBuild,PcdsPatchableInModule]
  gEfiMdePkg...Guid.PcdDebugPrintErrorLevel|0x80000000|UINT32|0x00000006

Nt32Pkg.dsc
[PcdsDynamicDefault]
  gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000040

將會Compile error...
Type [Dynamic] of PCD [gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel] in DSC file doesn't match the type [FixedAtBuild] defined in DEC file.

底下這句話是Spec中所提到的:
Some DEC files define multiple PCD types, but only one type can be used and will be chosen by the platform DSC file.
========================================

在編譯的時候Compiler會為每一支Module自動產生AutoGen.h與AutoGen.c。
關於PCD的部分,它會將使用者在inf中所加入的PCD轉換到上述的兩個檔案,對於不同型態的PCD,則會有不同的處理方式。



以下將會介紹各種不同的PCD:


PcdsFeatureFlag

FeatureFlag顧名思義可以用它來表示一個Feature是On或Off,所以它是一個Boolean值TRUE或FALSE。
可從下面的例子看出,PcdsFeatureFlag型態的PCD,將會被轉換成const的常數,也意味的它只能被唯讀。

Example: 
DSC
[PcdsFeatureFlag]
  gEfiMdePkgTokenSpaceGuid.PcdUgaConsumeSupport|TRUE

AutoGen.h
#define _PCD_TOKEN_PcdUgaConsumeSupport  11U
#define _PCD_VALUE_PcdUgaConsumeSupport  ((BOOLEAN)1U)
extern const  BOOLEAN  _gPcd_FixedAtBuild_PcdUgaConsumeSupport;
#define _PCD_GET_MODE_BOOL_PcdUgaConsumeSupport  _gPcd_FixedAtBuild_PcdUgaConsumeSupport
//#define _PCD_SET_MODE_BOOL_PcdUgaConsumeSupport  ASSERT(FALSE)  // It is not allowed to set value for a FIXED_AT_BUILD PCD

AutoGen.c
GLOBAL_REMOVE_IF_UNREFERENCED const BOOLEAN _gPcd_FixedAtBuild_PcdUgaConsumeSupport = _PCD_VALUE_PcdUgaConsumeSupport;
========================================

PcdsFixedAtBuild

FixedAtBuild與FeatureFlag挺相似的,最大的不同則是在於它可以宣告多種不同的變數型別,而不是只有局限於Boolean而已。

Example:
DSC
[PcdsFixedAtBuild]
  gEfiMdePkgTokenSpaceGuid.PcdPciExpressBaseAddress|0xE0000000

AutoGen.h
#define _PCD_TOKEN_PcdPciExpressBaseAddress  16U
#define _PCD_VALUE_PcdPciExpressBaseAddress  0xE0000000ULL
extern const  UINT64  _gPcd_FixedAtBuild_PcdPciExpressBaseAddress;
#define _PCD_GET_MODE_64_PcdPciExpressBaseAddress  _gPcd_FixedAtBuild_PcdPciExpressBaseAddress
//#define _PCD_SET_MODE_64_PcdPciExpressBaseAddress  ASSERT(FALSE)  // It is not allowed to set value for a FIXED_AT_BUILD PCD

AutoGen.c
GLOBAL_REMOVE_IF_UNREFERENCED const UINT64 _gPcd_FixedAtBuild_PcdPciExpressBaseAddress = _PCD_VALUE_PcdPciExpressBaseAddress;
========================================

PcdsPatchableInModule

宣告成PatchableInModule與上面兩種型態的PCD的最大不同是,它可以在Runtime時去變動它的數值。
然後它還有一個特質,通過下面的範例可以注意到,它被宣告成一個全域變數(Global Variable),在PE/COFF的檔案格式中,而全域變數將會被初始化並放置於Data Section之中。並有機會透過外部工具直接修改。
記得Compile完所產生的.efi檔案都是PE(Portable Executable)格式,有興趣的人可以用7z將.efi打開看看。

由於它只被宣告一個全域變數,所以只有同一個Module能夠取存,不能透過它與各Module相互傳遞數值。

Example:
DSC
[PcdsPatchableInModule]
  gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000000

AutoGen.h
#define _PCD_TOKEN_PcdDebugPrintErrorLevel  17U
#define _PCD_PATCHABLE_VALUE_PcdDebugPrintErrorLevel  ((UINT32)0x80000040U)
extern volatile   UINT32  _gPcd_BinaryPatch_PcdDebugPrintErrorLevel;
#define _PCD_GET_MODE_32_PcdDebugPrintErrorLevel  _gPcd_BinaryPatch_PcdDebugPrintErrorLevel
#define _PCD_SET_MODE_32_PcdDebugPrintErrorLevel(Value)  (_gPcd_BinaryPatch_PcdDebugPrintErrorLevel = (Value))

AutoGen.c
volatile  UINT32 _gPcd_BinaryPatch_PcdDebugPrintErrorLevel = _PCD_PATCHABLE_VALUE_PcdDebugPrintErrorLevel;
========================================

PcdsDynamic v.s. PcdsDynamicEx

這兩種型態的PCD都是集中於PCD Database作集中管理,有PEI與DXE兩個部分。
型態又可細分為以下幾種:
  • PcdsDynamicDefault and PcdsDynamicExDefault
  • PcdsDynamicHii and PcdsDynamicExHii
  • PcdsDynamicVpd and PcdsDynamicExVpd

在DEC所宣告的Section需要為PcdsDynamic或PcdsDynamicEx,而在DSC中則是它們的子分類。

PcdsDynamicDefault

DynamicDefault型態的PCD的最大優勢是在於,各個Module之間可以藉由這個PCD來互相傳遞數值。
所以它有著多樣化的應用方式,例如預先把某個不會變動的值從HW讀出來,並存到某個PCD裡面,這樣接下來其它的Module就可以不用再經過繁複的動作去存取HW上的值,直接透過PCD即可。

可以看到下面的例子,它會去透過一個Library去存取數值,而不是宣告一個常數或全域變數。

還可注意到一點,在DEC中所定的Token與AutoGen.h的數值並不相同!
其實Spec有提到,不管DEC中如何訂,Compiler都會自己產生一組,這應該是為了避免人為因素而有重複的狀況。
Although the token is auto-generated. A value is required for the Token field; if it is not defined, then the the build will fail. 

Example:
DEC
[PcdsDynamic, PcdsDynamicEx]
  gEfiMdeModulePkgTokenSpaceGuid.PcdS3BootScriptTablePrivateDataPtr|0x0|UINT64|0x00030000

DSC
[PcdsDynamicDefault] 
  gEfiMdeModulePkgTokenSpaceGuid.PcdS3BootScriptTablePrivateDataPtr|0x0

AutoGen.h
#define _PCD_TOKEN_PcdS3BootScriptTablePrivateDataPtr  1U
#define _PCD_GET_MODE_64_PcdS3BootScriptTablePrivateDataPtr  LibPcdGet64(_PCD_TOKEN_PcdS3BootScriptTablePrivateDataPtr)
#define _PCD_SET_MODE_64_PcdS3BootScriptTablePrivateDataPtr(Value)  LibPcdSet64(_PCD_TOKEN_PcdS3BootScriptTablePrivateDataPtr, (Value))
========================================

PcdsDynamicExDefault

多了Ex的DynamicExDefault與DynamicDefault很相似,但如果一個Module不是同時間與整個Platform一起Build的,而是一個外部的Binary,Platform與Binary又需要互相存取某個PCD的數值的話,這時候一開始該PCD就需要宣告成DynamicEx型態的PCD。
但外部的Binary在使用DynamicEx PCD時,必須先知道該PCD的GUID與Token值,才能透過LibPcdGetExXX或LibPcdSetExXX來存取。

Example:
DEC
[PcdsDynamic, PcdsDynamicEx]
  gEfiMdeModulePkgTokenSpaceGuid.PcdS3BootScriptTablePrivateDataPtr|0x0|UINT64|0x00030000

DSC
[PcdsDynamicExDefault] 
  gEfiMdeModulePkgTokenSpaceGuid.PcdS3BootScriptTablePrivateDataPtr|0x0

AutoGen.h
#define _PCD_TOKEN_PcdS3BootScriptTablePrivateDataPtr  196608U
#define _PCD_GET_MODE_64_PcdS3BootScriptTablePrivateDataPtr  LibPcdGetEx64(&gEfiMdeModulePkgTokenSpaceGuid, _PCD_TOKEN_PcdS3BootScriptTablePrivateDataPtr)
#define _PCD_SET_MODE_64_PcdS3BootScriptTablePrivateDataPtr(Value)  LibPcdSetEx64(&gEfiMdeModulePkgTokenSpaceGuid, _PCD_TOKEN_PcdS3BootScriptTablePrivateDataPtr, (Value))
========================================

PcdsDynamicHii

HII型態的PCD在DSC需定好它所對應的Variable Name、GUID、Offset(位於Variable Data的哪個Offset)還有Default Value。
如果系統中不存在著該Variable,那麼就會從PCD取得DSC所定好的預設值,否則就會回傳該Variable的Data加上Offset。

Example:
DEC
[PcdsDynamic, PcdsDynamicEx]
  gEfiIntelFrameworkModulePkgTokenSpaceGuid.PcdPlatformBootTimeOut|0xffff|UINT16|0x40000001

DSC
[PcdsDynamicHii]
  gEfiIntelFrameworkModulePkgTokenSpaceGuid.PcdPlatformBootTimeOut|L"Timeout"|gEfiGlobalVariableGuid|0x0|10

AutoGen.h
#define _PCD_TOKEN_PcdPlatformBootTimeOut  15U
#define _PCD_GET_MODE_16_PcdPlatformBootTimeOut  LibPcdGet16(_PCD_TOKEN_PcdPlatformBootTimeOut)
#define _PCD_SET_MODE_16_PcdPlatformBootTimeOut(Value)  LibPcdSet16(_PCD_TOKEN_PcdPlatformBootTimeOut, (Value))
========================================

PcdsDynamicExHii

多了Ex的DynamicExHii則意思跟DynamicEx很相似。

Example:
DEC
[PcdsDynamic, PcdsDynamicEx]
  gEfiIntelFrameworkModulePkgTokenSpaceGuid.PcdPlatformBootTimeOut|0xffff|UINT16|0x40000001

DSC
[PcdsDynamicExHii]
  gEfiIntelFrameworkModulePkgTokenSpaceGuid.PcdPlatformBootTimeOut|L"Timeout"|gEfiGlobalVariableGuid|0x0|10

AutoGen.h
#define _PCD_TOKEN_PcdPlatformBootTimeOut  1073741825U
#define _PCD_GET_MODE_16_PcdPlatformBootTimeOut  LibPcdGetEx16(&gEfiIntelFrameworkModulePkgTokenSpaceGuid, _PCD_TOKEN_PcdPlatformBootTimeOut)
#define _PCD_SET_MODE_16_PcdPlatformBootTimeOut(Value)  LibPcdSetEx16(&gEfiIntelFrameworkModulePkgTokenSpaceGuid, _PCD_TOKEN_PcdPlatformBootTimeOut, (Value))
========================================

PcdsDynamicVpd and PcdsDynamicExVpd

VPD (Vital Product Data),在NVS會有一塊區域被用來儲存VPD的資料,與其它Dynamic PCD不同,VPD只能唯讀,無法在執行期間去修改它的數值。
關於VPD的部分,目前我還沒有機會用到。

以上就先介紹的這個部分,下一篇再來介紹應用與原理。

2 則留言:

  1. 哈囉 想請教一下 PCD生命週期是只有boot time
    也就是說,進入SHELL PCD就沒了嗎
    也就是說在SHELL下面寫TOOL也沒辦法去SET PCD嗎

    回覆刪除
  2. Hi~
    在Shell底下還是可以存取Dynamic PCD,等到了要進OS時,呼叫了gBS->ExitBootServices後,一般的非Runtime driver都會被unload掉,此時才無法使用。

    普通的Fixed PCD不受影響,因為它只是一個#define~~

    Thanks.

    回覆刪除