【UEFI基础】PCD
簡介
本文將要介紹的是在UEFI中經常使用到的一種特殊的數據結構PCD,參考內容如下:
- 《edk-ii-pcd-specification.pdf》
- 《edk-ii-dsc-specification.pdf》
- 《edk-ii-build-specification.pdf》
- 《edk-ii-dec-specification.pdf》
- 《edk-ii-idf-specification.pdf》
- EDK代碼
PCD的全稱是Platform Configuration Database,它是一個存放了UEFI下可訪問數據的數據庫。
它的特點是可以在UEFI存在的大部分時間內訪問,在【UEFI實戰】在庫中使用全局變量中就曾經介紹過通過PCD來傳遞全局變量。這里說是大部分時間內,那是因為在諸如SEC階段,以及PEI、DXE階段的早期,某些PCD相關的模塊還沒有加載起來之前,這些類型的PCD還是不能訪問的。
基礎說明
PCD變量的格式如下:
TokenSpaceGuidCName.PcdCNameTokenSpaceGuidCName是一個GUID,PcdCName是一個變量名,兩者合起來構成了唯一的PCD變量。
PCD有下述的類型(主要的不同是在訪問的方式):
FeatureFlag PCD:它最終返回的是一個TRUE或者FALSE,用于判斷條件中;
PatchableInModule PCD:這種變量的值可以在編譯的時候確定,這個不算特別,特別的是它可以在編譯完成的二進制文件上通過工具來修改值;
FixedAtBuild PCD:靜態值,在編譯的時候確定,整個UEFI階段不可變;
Dynamic?PCD:前面的三種類型可以認為是靜態的PCD,而這里以及之后的是動態的PCD;它的特點是可以在UEFI運行的過程中通過Set宏來修改值;在《edk-ii-build-specification.pdf》中有說明該種類型的PCD必須在DSC中在列一遍,但是實際使用似乎并不是必須的;
DynamicEx?PCD:跟Dynamic PCD類似,算是加強版,使用宏PcdGetEx/PcdSetEx來訪問變量;
需要注意的是上面的類型并不是在一個SPEC中定義的,前面的4中是滿足EDKII規范,而最后一個滿足的是PI規范,這個對使用的影響還不是很確定。
上述說的PCD是按訪問的方式來分類的。
另外還需要關注的是PCD變量的值可能的類型,有BOOLEAN類型,整型(UINT8,UINT16等)以及VOID *類型。
需要注意的是BOOLEAN這種類型FixedAtBuild PCD和FeatureFlag PCD中都可以存在,但是兩者實際上是不同的,訪問使用的宏也是不同的,前者是FixedPcdGetBool()而后者是FeaturePcdGet()。但是后面會看到,從實現上來講兩者是一致的......
示例
下面先介紹一個簡單的示例。
1. 在dec文件中聲明PCD的基本信息,如下所示:
[PcdsDynamic]gUefiOemPkgTokenSpaceGuid.PcdOemVersion|0xFFFFFFFF|UINT32|0x40000001在《edk-ii-dec-specification.pdf》中有說明上述內容的意義。
它分為兩個部分,第一個部分[PcdsDynamic]聲明了PCD的類型,PCD的多種類型后續會一一介紹。
第二部分的內容如下:
#加注釋部分這里沒有用到;
TokeSpaceGuidCname是一個GUID,也是在DEC中聲明的,如下所示:
[Guids]# // {6BB4BEC8-23D8-40AF-8F24-99E7AC8601FD}gUefiOemPkgTokenSpaceGuid = { 0x6bb4bec8, 0x23d8, 0x40af, { 0x8f, 0x24, 0x99, 0xe7, 0xac, 0x86, 0x01, 0xfd } }PcdCname是PCD的名稱,后續使用的時候就需要用到它。
DefaultValue是PCD的默認值,DatumType是PCD的類型,Token是一個32位的整型,在DEC中每個PCD都有一個獨一無二的Token。
2. 在DSC中設置PCD的值:
# // OEM defined dynamic PCD.gUefiOemPkgTokenSpaceGuid.PcdOemVersion|0x12345678這個設置并不是必須的,如果沒有整個設置,就使用在DEC中默認的值。
3. 使用:
DEBUG ((EFI_D_ERROR, "[beni]Version: 0x%x.\n", PcdGet32(PcdOemVersion)));要使用PcdGet32這種的宏,需要包含PcdLib這個庫。
還需要在inf中包含該PCD:
[Pcd]gUefiOemPkgTokenSpaceGuid.PcdOemVersion以上的代碼就會打印出[beni]Version: 0x12345678。
該部分代碼可以在vUDK2017: https://github.com/tianocore/edk2.git Tag vUDK2017.找到。
實現說明
下面說明各種PCD的實現。
FixedAtBuild PCD
不同的PCD類型實現方式有差異,這里首先介紹最簡單的FixedAtBuild PCD。
下面是一個例子:
1. 首先聲明一系列的PCD:
[PcdsFixedAtBuild] gUefiOemPkgTokenSpaceGuid.PcdTestVar1|0xA5|UINT8|0x20000001 gUefiOemPkgTokenSpaceGuid.PcdTestVar2|0xA5A5|UINT16|0x20000002 gUefiOemPkgTokenSpaceGuid.PcdTestVar3|0xA5A5A5A5|UINT32|0x20000003 gUefiOemPkgTokenSpaceGuid.PcdTestVar4|0xA5A5A5A5A5A5A5A5|UINT64|0x200000042. 然后在一個模塊中使用:
EFI_STATUS EFIAPI PcdTestDriverEntry (IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable) {EFI_STATUS Status = EFI_SUCCESS;DEBUG ((EFI_D_ERROR, "[beni]PcdTestDriverEntry Start.\n"));DEBUG ((EFI_D_ERROR, "[beni]PcdTestVar1: 0x%x.\n", PcdGet8 (PcdTestVar1)));DEBUG ((EFI_D_ERROR, "[beni]PcdTestVar2: 0x%x.\n", PcdGet16 (PcdTestVar2)));DEBUG ((EFI_D_ERROR, "[beni]PcdTestVar3: 0x%x.\n", PcdGet32 (PcdTestVar3)));DEBUG ((EFI_D_ERROR, "[beni]PcdTestVar4: 0x%x.\n", PcdGet64 (PcdTestVar4)));DEBUG ((EFI_D_ERROR, "[beni]PcdTestDriverEntry End.\n"));return Status; }3. 編譯后查看該模塊編譯目錄中的AutoGen.c文件,可以看到如下的內容:
// Definition of PCDs used in this module GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdTestVar1 = _PCD_VALUE_PcdTestVar1; GLOBAL_REMOVE_IF_UNREFERENCED const UINT16 _gPcd_FixedAtBuild_PcdTestVar2 = _PCD_VALUE_PcdTestVar2; GLOBAL_REMOVE_IF_UNREFERENCED const UINT32 _gPcd_FixedAtBuild_PcdTestVar3 = _PCD_VALUE_PcdTestVar3; GLOBAL_REMOVE_IF_UNREFERENCED const UINT64 _gPcd_FixedAtBuild_PcdTestVar4 = _PCD_VALUE_PcdTestVar4;4. 這里的_PCD_VALUE_PcdTestVarx的值在AutoGen.h中聲明:
// Definition of PCDs used in this module#define _PCD_TOKEN_PcdTestVar1 107U #define _PCD_SIZE_PcdTestVar1 1 #define _PCD_GET_MODE_SIZE_PcdTestVar1 _PCD_SIZE_PcdTestVar1 #define _PCD_VALUE_PcdTestVar1 0xA5U extern const UINT8 _gPcd_FixedAtBuild_PcdTestVar1; #define _PCD_GET_MODE_8_PcdTestVar1 _gPcd_FixedAtBuild_PcdTestVar1 //#define _PCD_SET_MODE_8_PcdTestVar1 ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD#define _PCD_TOKEN_PcdTestVar2 108U #define _PCD_SIZE_PcdTestVar2 2 #define _PCD_GET_MODE_SIZE_PcdTestVar2 _PCD_SIZE_PcdTestVar2 #define _PCD_VALUE_PcdTestVar2 0xA5A5U extern const UINT16 _gPcd_FixedAtBuild_PcdTestVar2; #define _PCD_GET_MODE_16_PcdTestVar2 _gPcd_FixedAtBuild_PcdTestVar2 //#define _PCD_SET_MODE_16_PcdTestVar2 ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD#define _PCD_TOKEN_PcdTestVar3 109U #define _PCD_SIZE_PcdTestVar3 4 #define _PCD_GET_MODE_SIZE_PcdTestVar3 _PCD_SIZE_PcdTestVar3 #define _PCD_VALUE_PcdTestVar3 0xA5A5A5A5U extern const UINT32 _gPcd_FixedAtBuild_PcdTestVar3; #define _PCD_GET_MODE_32_PcdTestVar3 _gPcd_FixedAtBuild_PcdTestVar3 //#define _PCD_SET_MODE_32_PcdTestVar3 ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD#define _PCD_TOKEN_PcdTestVar4 110U #define _PCD_SIZE_PcdTestVar4 8 #define _PCD_GET_MODE_SIZE_PcdTestVar4 _PCD_SIZE_PcdTestVar4 #define _PCD_VALUE_PcdTestVar4 0xA5A5A5A5A5A5A5A5ULL extern const UINT64 _gPcd_FixedAtBuild_PcdTestVar4; #define _PCD_GET_MODE_64_PcdTestVar4 _gPcd_FixedAtBuild_PcdTestVar4 //#define _PCD_SET_MODE_64_PcdTestVar4 ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD5. 再回到2中代碼里面我們訪問PCD的方式如下:
PcdGet8 (PcdTestVar1))將它展開后的結果就是_PCD_GET_MODE_8_PcdTestVar1,它在上述的AutoGen.h就已經定義了,最終的值就是0xA5。
也就是說,對應FixedAtBuild PCD來說,它就是在編譯的時候通過宏的方式生成的。
也因此它是固定不變的一個值。
FeatureFlag PCD
FeatureFlag PCD跟FixedAtBuild是一樣的,它相當于類型是BOOELAN的FixedAtBuild PCD,從下面的示例可以看出來:
1. 創建一個FeatureFlag PCD:
[PcdsFeatureFlag]gUefiOemPkgTokenSpaceGuid.PcdTestFeatureVar|FALSE|BOOLEAN|0x300000012. 然后在模塊中使用它:
DEBUG ((EFI_D_ERROR, "[beni]PcdTestBooleanVar: %d\n", FeaturePcdGet (PcdTestFeatureVar)));DEBUG ((EFI_D_ERROR, "[beni]PcdTestBooleanVar: %d\n", PcdGetBool (PcdTestFeatureVar)));3. 注意這里使用了兩種方式來訪問這個變量,查看它的AutoGen.c文件:
GLOBAL_REMOVE_IF_UNREFERENCED const BOOLEAN _gPcd_FixedAtBuild_PcdTestFeatureVar = _PCD_VALUE_PcdTestFeatureVar;4. 再查看AutoGen.h文件:
#define _PCD_TOKEN_PcdTestFeatureVar 111U #define _PCD_SIZE_PcdTestFeatureVar 1 #define _PCD_GET_MODE_SIZE_PcdTestFeatureVar _PCD_SIZE_PcdTestFeatureVar #define _PCD_VALUE_PcdTestFeatureVar ((BOOLEAN)0U) extern const BOOLEAN _gPcd_FixedAtBuild_PcdTestFeatureVar; #define _PCD_GET_MODE_BOOL_PcdTestFeatureVar _gPcd_FixedAtBuild_PcdTestFeatureVar //#define _PCD_SET_MODE_BOOL_PcdTestFeatureVar ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD5. 可以從前綴看出來,它其實就是一個FixedAtBuild PCD。
6. 察看FeaturePcdGet和PcdGetBool兩個宏:
#define FeaturePcdGet(TokenName) _PCD_GET_MODE_BOOL_##TokenName #define PcdGetBool(TokenName) _PCD_GET_MODE_BOOL_##TokenName也可以看到,他們指向的是同一種東西。
Dynamic PCD
下面介紹Dynamic PCD,首先看一個例子:
1. 創建一個Dynamic PCD(其實就是最開始的那個示例):
[PcdsDynamic] gUefiOemPkgTokenSpaceGuid.PcdOemVersion|0xABCDDCBA|UINT32|0x400000012. 然后在模塊中使用:
DEBUG ((EFI_D_ERROR, "[beni]PcdOemVersion: 0x%x\n", PcdGet32 (PcdOemVersion)));3. 編譯后查看AutoGen.c文件,發現在該文件中沒有找到相關的內容;
4. 再查看AutoGen.h文件,可以看到如下內容:
#define _PCD_TOKEN_PcdOemVersion 13U #define _PCD_GET_MODE_32_PcdOemVersion LibPcdGet32(_PCD_TOKEN_PcdOemVersion) #define _PCD_GET_MODE_SIZE_PcdOemVersion LibPcdGetSize(_PCD_TOKEN_PcdOemVersion) #define _PCD_SET_MODE_32_PcdOemVersion(Value) LibPcdSet32(_PCD_TOKEN_PcdOemVersion, (Value)) #define _PCD_SET_MODE_32_S_PcdOemVersion(Value) LibPcdSet32S(_PCD_TOKEN_PcdOemVersion, (Value))這里有幾個明顯的區別:
1)AutoGen.c中沒有了PCD變量的定義;
2)PCD訪問方式變了,Dynamic對應的是函數;
3)Set函數出現了;
關于PCD庫函數的實現,這里以LibPcdGet32()為例(就是上例中用到的),它的實現如下:
/**This function provides a means by which to retrieve a value for a given PCD token.Returns the 32-bit value for the token specified by TokenNumber. @param[in] TokenNumber The PCD token number to retrieve a current value for.@return Returns the 32-bit value for the token specified by TokenNumber.**/ UINT32 EFIAPI LibPcdGet32 (IN UINTN TokenNumber) {return GetPcdProtocol()->Get32 (TokenNumber); }也就是說這里要依賴于Protocol(DXE階段是Protocol,而PEI階段是PPI)來完成操作。
這Protocol在Pcd.info模塊中安裝。
Pcd.inf模塊分為PEI和DXE兩個版本,分別放在PEI階段和DXE階段的最前面,只有整個模塊初始化完成之后,才能夠開始正常使用PCD宏來訪問Dynamic PCD變量。
以DXE階段的Pcd.inf模塊為例,它主要做了兩件事情:
1. 初始化該階段使用的PCD數據庫;
2. 安裝各種處理PCD需要的Protocol;
在這個模塊中,安裝的主要的Protocol如下:
/// /// This service abstracts the ability to set/get Platform Configuration Database (PCD). /// 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()函數了。
在上述GetX和SetX的函數實現中,最重要的是如下的函數:
GetWorker():
/**Get the PCD entry pointer in PCD database.This routine will visit PCD database to find the PCD entry according to giventoken number. The given token number is autogened by build tools and it will be translated to local token number. Local token number contains PCD's type and offset of PCD entry in PCD database.@param TokenNumber Token's number, it is autogened by build tools@param GetSize The size of token's value@return PCD entry pointer in PCD database**/ VOID * GetWorker (IN UINTN TokenNumber,IN UINTN GetSize)SetWorker():
/**Set value for an PCD entry@param TokenNumber Pcd token number autogenerated by build tools.@param Data Value want to be set for PCD entry@param Size Size of value.@param PtrType If TRUE, the type of PCD entry's value is Pointer.If False, the type of PCD entry's value is not Pointer.@retval EFI_INVALID_PARAMETER If this PCD type is VPD, VPD PCD can not be set.@retval EFI_INVALID_PARAMETER If Size can not be set to size table.@retval EFI_INVALID_PARAMETER If Size of non-Ptr type PCD does not match the size information in PCD database. @retval EFI_NOT_FOUND If value type of PCD entry is intergrate, but not inrange of UINT8, UINT16, UINT32, UINT64@retval EFI_NOT_FOUND Can not find the PCD type according to token number. **/ EFI_STATUS SetWorker (IN UINTN TokenNumber,IN VOID *Data,IN OUT UINTN *Size,IN BOOLEAN PtrType)GetWorker()的作用就是在PCD數據庫里面找到對應的PCD的指針,而SetWork()的作用就是找到指針然后賦值。
到這里為止,最需要了解的就是PCD數據庫是如何建立的?
從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;這里主要進行了PCD數據庫的創建,它分為兩個步驟:
1. 通過LocateExPcdBinary()從FFS中獲取PCD已初始化變量;
2. 為未初始化的變量(默認全0)分配空間(這樣做的目的應該是為了減少生成二進制的大小);
接下來需要關注的就是這個放在FFS中的PCD數據是從何而來的?
查看LocateExPcdBinary()函數里面的代碼:
//// Search the External Pcd database from one section of current FFS, // and read it to memory//Status = GetSectionFromFfs (EFI_SECTION_RAW,0,(VOID **) &DxePcdDbBinary,&DxePcdDbSize);ASSERT_EFI_ERROR (Status);從中可以看到PCD數據是存放在當前FFS的第一個Section開始的數據。
關于FFS,在之前的文章【UEFI基礎】EDK編譯生成的二進制的結構中介紹過,這里直接打開下面的文檔查看:
使用二進制查看工具打開該文件,搜索PCD對應的GUID(gPcdDataBaseSignatureGuid),結果如下:
因此Dynamic PCD的數據是在編譯的時候初始化并存放在BIOS二進制中的,然后在BIOS運行過程中會獲取這些數據,并存放到內存中,后續就可以修改了。
DynamicEx類型的基本上一致,這里不再說明。
PatchableInModule PCD
最后剩下的是PatchableInModule PCD,下面是一個例子:
1. 在DEC中聲明:
[PcdsPatchableInModule] gUefiOemPkgTokenSpaceGuid.PcdPatchableVar|0x00ABCDEF|UINT32|0x500000012. 在模塊中使用:
DEBUG ((EFI_D_ERROR, "[beni]PcdPatchableVar: 0x%x\n", PcdGet32 (PcdPatchableVar)));3. 查看AutoGen.c文件:
volatile UINT32 _gPcd_BinaryPatch_PcdPatchableVar = _PCD_PATCHABLE_VALUE_PcdPatchableVar; GLOBAL_REMOVE_IF_UNREFERENCED UINTN _gPcd_BinaryPatch_Size_PcdPatchableVar = 4;4. 這里定義了PCD相關的兩個變量,一個是值,注意它的類型修飾符是volatile,另一個是PCD的大小。
5. 進一步查看AutoGen.h文件,可以看到_gPcd_BinaryPatch_PcdPatchableVar當前的值:
#define _PCD_TOKEN_PcdPatchableVar 112U #define _PCD_PATCHABLE_VALUE_PcdPatchableVar ((UINT32)0x00ABCDEFU) extern volatile UINT32 _gPcd_BinaryPatch_PcdPatchableVar; #define _PCD_GET_MODE_32_PcdPatchableVar _gPcd_BinaryPatch_PcdPatchableVar #define _PCD_PATCHABLE_PcdPatchableVar_SIZE 4 #define _PCD_GET_MODE_SIZE_PcdPatchableVar _gPcd_BinaryPatch_Size_PcdPatchableVar extern UINTN _gPcd_BinaryPatch_Size_PcdPatchableVar; #define _PCD_SET_MODE_32_PcdPatchableVar(Value) (_gPcd_BinaryPatch_PcdPatchableVar = (Value)) #define _PCD_SET_MODE_32_S_PcdPatchableVar(Value) ((_gPcd_BinaryPatch_PcdPatchableVar = (Value)), RETURN_SUCCESS)6. 它的訪問方式與FixedAtBuild PCD沒有什么兩樣。
對于PatchableInModule PCD來說,唯一的差別就在于,變量是volatile的,但是這個volatile具體體現在哪里呢?我們如何對它進行Patch?
因為該PCD是可以通過外部工具來修改的,那么有一點是可以肯定的,即它是可見的,通過查看BIOS二進制就可以找到。
首先找OVMF.fd(這個是最終生成的BIOS二進制)中尋找,但是沒有找到,再在DXEFV.Fv中尋找,發現了它的蹤跡:
它的位置是在當前模塊(即打印該PCD的模塊)的只讀數據區中。
同時在對應的EFI文件中也能找到:
其地址在與AutoGen同目錄的map文件中也有說明:
上圖中也可以發現,其它類型的PCD是沒有這個。
因此理論上可以通過工具來修改這個值(在生成最終的OVMF.fd之前)。
而實際上,在EDK的源代碼中,提供了操作PatchableInModule?PCD的工具:
因此可以利用這兩個工具來修改PCD。
這里將這兩個工具(以及依賴文件)放到下述的編譯中間目錄:
首先是使用GenPatchPcdTable.exe來生成文件,該文件中包含所有的PatchableInModule PCD:
使用該工具之后生成了一個文件:
其內容如下:
(會看到跟之前二進制里面的偏移不一樣,因為是重新編譯過,不要在意...)
然后通過PatchPcdValue.exe來修改PCD:
這里將PcdPatchableVar的值修改成了0x88888888,比較修改前后的值:
可以看到確實是修改成功了!
具體GenPatchPcdTable.exe和PatchPcdValue.exe可以參考源代碼中BaseTools\UserManuals目錄下的說明。
以上的例子只是手動的修改efi文件,但是如果在編譯的時候自動完成這個動作,或者在整個二進制生成之后修改PatchableInModule PCD呢?
目前這還是個疑問。
以上,就是對于UEFI PCD的所有內容的介紹。
總結
以上是生活随笔為你收集整理的【UEFI基础】PCD的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Gmail中基本html
- 下一篇: 《影响力》读书总结(一):影响力的武器