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的部分,目前我還沒有機會用到。

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

2013年12月17日 星期二

Package, Component, DEC and DSC

Package

EDK II與EDK最大的差異就是在於多了Package這個模組化的設計,把具有相同類型的Component、Library與各種Declaration放在這同一個目錄。
而每一個Package都有.dec (EDK II Package Declaration File)還有.dsc (EDK II Package Description File)這兩個檔案。

Component

每一個Component可以當作是EFI的執行檔,編譯完會產生出.efi的檔案。
而Component會在它的inf檔中宣告它的MODULE_TYPE,總共有以下這幾種類型:
BASE, SEC, PEI_CORE, PEIM,
DXE_CORE, DXE_DRIVER, DXE_RUNTIME_DRIVER, DXE_SMM_DRIVER, DXE_SAL_DRIVER, UEFI_DRIVER, UEFI_APPLICATION

但要注意的是BASE這種類型只有Library可以使用。
對一般的開發人員來說,常用的應該只有PEIM、 DXE_DRIVER, DXE_RUNTIME_DRIVER, DXE_SMM_DRIVER, UEFI_DRIVER與UEFI_APPLICATION。

DEC (EDK II Package Declaration File)

Package中有.dec這個檔案來宣告提供給內外部的Component所使用的interface,如:header files所在的位置、Library、GUID、protocol、PCD的宣告...等。
等於它是一個提供者的角色,對於使用者(Component、Library)來說,有用到該Package的資源的話,就需要在其inf加入這個DEC。

DEC中包含了以下這些區塊:
Defines、Includes、LibraryClasses、Guids、Ppis、Protocols與Pcds。

========================================
Includes:描述這個Package的header files都集中放在哪一個目錄底下,所以常可在實際的目錄下看到Library、Protocol、Ppi等資料夾存在。
[Includes]
  Include


[Includes.IA32]
  Include/Ia32

[Includes.X64]
  Include/X64


========================================
Guids、Ppis、Protocols:定義了這個Package所提供的Guids、Ppis與Protocols,在以往EDK使用某個GUID的變數時,都必須在.inf加入有定義這個變數的Library,不然就是要自己宣告一個。
而EDK II中我們只要加入包含它所被宣告的DEC即可使用,這部分Compiler都已經幫我們處理好了。
[Guids]
  #GuidCName = {xxxxxxxx,xxxx,xxxx,{xx,xx,xx,xx,xx,xx,xx,xx}}

[Ppis]
  #PpiGuidCName = {xxxxxxxx,xxxx,xxxx,{xx,xx,xx,xx,xx,xx,xx,xx}}

[Protocols]
  #ProtocolGuidCName = {xxxxxxxx,xxxx,xxxx,{xx,xx,xx,xx,xx,xx,xx,xx}}


========================================
以下描述的各種PCD型態的宣告與初始值。
[PcdsFeatureFlag]
  #FeatureFlag PCD is BOOLEAN type, the value is TRUR or FALSE.
  #PcdTokenSpaceCGuidName.PcdName|TRUE|BOOLEAN|TokenNumber
  #PcdTokenSpaceCGuidName.PcdName|FALSE|BOOLEAN|TokenNumber

[PcdsFixedAtBuild]
  #PcdTokenSpaceCGuidName.PcdName|DefaultValue|DataType|TokenNumber

[PcdsPatchableInModule]
  #PcdTokenSpaceCGuidName.PcdName|DefaultValue|DataType|TokenNumber

[PcdsDynamic]
  #PcdTokenSpaceCGuidName.PcdName|DefaultValue|DataType|TokenNumber

[PcdsDynamicEx]
  #PcdTokenSpaceCGuidName.PcdName|DefaultValue|DataType|TokenNumber


可以注意的是,在DEC中的PCD宣告可以是複合式的宣告。
[PcdsFixedAtBuild, PcdsPatchableInModule, PcdsDynamic, PcdsDynamicEx]  gEfiMdePkgTokenSpaceGuid.PcdPciExpressBaseAddress|0xE0000000|UINT64|0x0000000a


DSC (EDK II Package Description File)

每一個Package內通常會有一個.dsc的檔案,用來指定要編譯那些Component與Library,並且定義每一個PCD的類型與實際的初始值,因為DEC檔內只是宣告PCD型態與初始值,而實際上的定義,則是在DSC之中。

DSC中包含了以下這些區塊:
Defines、LibraryClasses、與Pcds。

2013年12月11日 星期三

UDK vs. EDK II


在網路上會看到有UDK (UEFI Development Kits)跟EDK II這兩個相似的東西,但這兩者有甚麼樣的不同?
  1. EDK II是基於開放原始碼,並由一群自願者所開發與維護。
  2. 而UDK是由Intel基於某一個EDK II的特定版本所維護並發佈出來的,相對來講會來的比較穩定。
可經由此去取得EDK II與UDK。
http://sourceforge.net/apps/mediawiki/tianocore/index.php?title=EDK2

UEFI Detect Utility - 2014




已做部分更新,先前版本在某些機台會無法使用。

解壓密碼:leon
UefiDetectUtility_131211.7z