The Platform Configuration Database (PCD) is a database that contains a variety of current platform settings or directives that can be accessed by a driver or application. You can checkout edk2 specification https://edk2-docs.gitbook.io/edk-ii-pcd-specification/ or https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/edkii-platform-config-database-entries-paper.pdf for more explanation on PCD. PCD entry is also called PCD, so we will use this term further. The PCD entry is defined in a DEC file in a format: ``` .||| ``` `` is a GUID value, `` is a 32-bit value. Together they are used to uniqely identify PCD. First let's declare a Token Space that would contain all our PCDs. Usually in is defined as a `gTokenSpaceGuid`, so add this to our `UefiLessonsPkg/UefiLessonsPkg.dec`: ``` [Guids] ... gUefiLessonsPkgTokenSpaceGuid = {0x150cab53, 0xad47, 0x4385, {0xb5, 0xdd, 0xbc, 0xfc, 0x76, 0xba, 0xca, 0xf0}} ``` As for the `` values, usually the package creators just start to write them sequentially from `0x00000001`. Also very often when they want to indicate that some PCDs are belong to one logical group, they can start using the token numbers to indicate that. For example the token groups can be `0x1XXXXXXX`, `0x2XXXXXXX`, and so on, or something similar. In any way as the package evolve some PCD are getting added and some are getting removed. And if you use such sequential numbering this can give you headache. For example at one point you can end up in situation when you have PCDs with the tokens `0x0000000A` and `0x0000000B` and the most logical way to put your new PCD is after the one with a `0x0000000A` token. Off course you can assign PCD token to `0x0000000C` and do it, but what is the point of a sequential numbering then? Because of that I've created `./scripts/genToken.sh` script that generates random 4-byte token number: ``` #!/bin/bash ## # This is a simple script that generates a random 4-byte hex value for a PCD Token ## hexdump -vn4 -e'"0x%08X\n"' /dev/urandom ``` The usage is simple as this: ``` $ ./scripts/genToken.sh 0x3B81CDF1 ``` Now we can define our PCD in the same `*.dec` file that we've used to define `gUefiLessonsPkgTokenSpaceGuid`. Let's start with a PCD `UINT8 PcdInt8 = 0x88`: ``` [PcdsFixedAtBuild] gEfiUefiLessonsPkgTokenSpaceGuid.PcdInt8|0x88|UINT8|0x3B81CDF1 ``` Now create an app `PCDLesson` with the following code in its entry point function: ``` Print(L"PcdInt8=0x%x\n", FixedPcdGet8(PcdInt8)); ``` To use `FixedPcdGet8` in our code we need to add the necessary include: ``` #include ``` If you check out this file (https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PcdLib.h) you'll see that `FixedPcdGet8` is simply a define statement: ``` #define FixedPcdGet8(TokenName) _PCD_VALUE_##TokenName ``` If we try to build our app now, build will fail, as we don't have such define in our app: ``` /home/kostr/tiano/edk2/MdePkg/Include/Library/PcdLib.h:97:45: error: ‘_PCD_VALUE_PcdInt8’ undeclared (first use in this function) 97 | #define FixedPcdGet8(TokenName) _PCD_VALUE_##TokenName | ^~~~~~~~~~~ ``` To fix this we need to add this PCD to our app `*.inf` file: ``` [FixedPcd] gUefiLessonsPkgTokenSpaceGuid.PcdInt8 ``` Also we need to include "dec" file that defines this PCD: ``` [Packages] ... UefiLessonsPkg/UefiLessonsPkg.dec ``` Now compilation would succeed. If you check out the content of autogenerated files `AutoGen.h`/`AutoGen.c`, you'll see, that our PCD is there: - `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/AutoGen.h` ``` // Definition of PCDs used in this module #define _PCD_TOKEN_PcdInt8 0U #define _PCD_SIZE_PcdInt8 1 #define _PCD_GET_MODE_SIZE_PcdInt8 _PCD_SIZE_PcdInt8 #define _PCD_VALUE_PcdInt8 0x88U extern const UINT8 _gPcd_FixedAtBuild_PcdInt8; #define _PCD_GET_MODE_8_PcdInt8 _gPcd_FixedAtBuild_PcdInt8 //#define _PCD_SET_MODE_8_PcdInt8 ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD ``` - `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/AutoGen.c` ``` // Definition of PCDs used in this module GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdInt8 = _PCD_VALUE_PcdInt8; ``` So in our case the preprocessor expands code like this: ``` FixedPcdGet8(PcdInt8) -> _PCD_VALUE_PcdInt8 -> 0x88U ``` If you execute app code under OVMF you would get correct value printed: ``` FS0:\> PCDLesson.efi PcdInt8=0x88 ``` There are multiple types of PCDs. `FixedAtBuild` PCD is only one of them. In our code we've used `FixedPcdGet8` call to get PCD value, this call would only work if PCD is `FixedAtBuild`. However there is a generic `PcdGet8` call that can be used to get a value of PCD regardless its PCD type. [https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PcdLib.h](https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PcdLib.h): ``` #define PcdGet8(TokenName) _PCD_GET_MODE_8_##TokenName ``` In our case this would expand to: ``` PcdGet8(PcdInt8) -> _PCD_GET_MODE_8_PcdInt8 -> _gPcd_FixedAtBuild_PcdInt8 ``` The latter one is a variable that is defined in a `AutoGen.c` file: ``` GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdInt8 = _PCD_VALUE_PcdInt8 // =0x88U; ``` So as you can see result would be the same. The difference is that `PcdGet` would work with other PCD types, that we would cover in the next lessons. You can verify that this code: ``` Print(L"PcdInt8=%d\n", FixedPcdGet8(PcdInt8)); Print(L"PcdInt8=%d\n", PcdGet8(PcdInt8)); ``` Would produce the output: ``` FS0:\> PCDLesson.efi PcdInt8=0x88 PcdInt8=0x88 ``` # Other simple PCD types In the example above we've used `UINT8` as a `` of our PCD. Along with this type EDK2 also allows you to use other integer data types `UINT16`, `UINT32` `UINT64` integer data types and a `BOOLEAN` type. Add these PCDs to the `UefiLessonsPkg/UefiLessonsPkg.dec`: ``` [PcdsFixedAtBuild] ... gUefiLessonsPkgTokenSpaceGuid.PcdInt16|0x1616|UINT16|0x77DFB6E6 gUefiLessonsPkgTokenSpaceGuid.PcdInt32|0x32323232|UINT32|0xF2A48130 gUefiLessonsPkgTokenSpaceGuid.PcdInt64|0x6464646464646464|UINT64|0x652F4E29 gUefiLessonsPkgTokenSpaceGuid.PcdBool|TRUE|BOOLEAN|0x69E88A63 ``` And include them in our module `UefiLessonsPkg/PCDLesson/PCDLesson.inf`: ``` [FixedPcd] ... gUefiLessonsPkgTokenSpaceGuid.PcdInt16 gUefiLessonsPkgTokenSpaceGuid.PcdInt32 gUefiLessonsPkgTokenSpaceGuid.PcdInt64 gUefiLessonsPkgTokenSpaceGuid.PcdBool ``` This would populate them to the `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/AutoGen.h`: ``` #define _PCD_TOKEN_PcdInt16 0U #define _PCD_SIZE_PcdInt16 2 #define _PCD_GET_MODE_SIZE_PcdInt16 _PCD_SIZE_PcdInt16 #define _PCD_VALUE_PcdInt16 0x1616U extern const UINT16 _gPcd_FixedAtBuild_PcdInt16; #define _PCD_GET_MODE_16_PcdInt16 _gPcd_FixedAtBuild_PcdInt16 //#define _PCD_SET_MODE_16_PcdInt16 ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD #define _PCD_TOKEN_PcdInt32 0U #define _PCD_SIZE_PcdInt32 4 #define _PCD_GET_MODE_SIZE_PcdInt32 _PCD_SIZE_PcdInt32 #define _PCD_VALUE_PcdInt32 0x32323232U extern const UINT32 _gPcd_FixedAtBuild_PcdInt32; #define _PCD_GET_MODE_32_PcdInt32 _gPcd_FixedAtBuild_PcdInt32 //#define _PCD_SET_MODE_32_PcdInt32 ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD #define _PCD_TOKEN_PcdInt64 0U #define _PCD_SIZE_PcdInt64 8 #define _PCD_GET_MODE_SIZE_PcdInt64 _PCD_SIZE_PcdInt64 #define _PCD_VALUE_PcdInt64 0x6464646464646464ULL extern const UINT64 _gPcd_FixedAtBuild_PcdInt64; #define _PCD_GET_MODE_64_PcdInt64 _gPcd_FixedAtBuild_PcdInt64 //#define _PCD_SET_MODE_64_PcdInt64 ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD #define _PCD_TOKEN_PcdBool 0U #define _PCD_SIZE_PcdBool 1 #define _PCD_GET_MODE_SIZE_PcdBool _PCD_SIZE_PcdBool #define _PCD_VALUE_PcdBool 1U extern const BOOLEAN _gPcd_FixedAtBuild_PcdBool; #define _PCD_GET_MODE_BOOL_PcdBool _gPcd_FixedAtBuild_PcdBool //#define _PCD_SET_MODE_BOOL_PcdBool ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD ``` And to the `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/AutoGen.c`: ``` GLOBAL_REMOVE_IF_UNREFERENCED const UINT16 _gPcd_FixedAtBuild_PcdInt16 = _PCD_VALUE_PcdInt16; GLOBAL_REMOVE_IF_UNREFERENCED const UINT32 _gPcd_FixedAtBuild_PcdInt32 = _PCD_VALUE_PcdInt32; GLOBAL_REMOVE_IF_UNREFERENCED const UINT64 _gPcd_FixedAtBuild_PcdInt64 = _PCD_VALUE_PcdInt64; GLOBAL_REMOVE_IF_UNREFERENCED const BOOLEAN _gPcd_FixedAtBuild_PcdBool = _PCD_VALUE_PcdBool; ``` Everything is similar to the `UINT8` case. Like before you can use either `FixedPcdGet` or `PcdGet` API to get the PCD values: ``` Print(L"PcdInt16=0x%x\n", FixedPcdGet16(PcdInt16)); Print(L"PcdInt32=0x%x\n", FixedPcdGet32(PcdInt32)); Print(L"PcdInt64=0x%x\n", FixedPcdGet64(PcdInt64)); Print(L"PcdBool=0x%x\n", FixedPcdGetBool(PcdBool)); Print(L"PcdInt16=0x%x\n", PcdGet16(PcdInt16)); Print(L"PcdInt32=0x%x\n", PcdGet32(PcdInt32)); Print(L"PcdInt64=0x%x\n", PcdGet64(PcdInt64)); Print(L"PcdIntBool=0x%x\n", PcdGetBool(PcdBool)); ``` Once again the values would be the same: ``` FS0:\> PCDLesson.efi ... PcdInt16=0x1616 PcdInt32=0x32323232 PcdInt64=0x64646464 PcdBool=0x1 PcdInt16=0x1616 PcdInt32=0x32323232 PcdInt64=0x64646464 PcdIntBool=0x1 ``` # Expressions in initialization values It is possible to use expressions in initialization values. Keep in mind that when you use `|` character in you operations, you must put the expression inside the brackets `(...)`. Here are some examples: ``` [PcdsFixedAtBuild] ... gUefiLessonsPkgTokenSpaceGuid.PcdExpression|0xFF000000 + 0x00FFFFFF|UINT32|0x9C405222 gUefiLessonsPkgTokenSpaceGuid.PcdExpression_1|((0xFFFFFFFF & 0x000000FF) << 8) + 0x33|UINT32|0x5911C44B gUefiLessonsPkgTokenSpaceGuid.PcdExpression_2|(0x00000000 | 0x00100000)|UINT32|0xAD880207 gUefiLessonsPkgTokenSpaceGuid.PcdExpression_3|(56 < 78) || !(23 > 44)|BOOLEAN|0x45EDE955 ``` Populate them to the INF file: ``` [FixedPcd] ... gUefiLessonsPkgTokenSpaceGuid.PcdExpression gUefiLessonsPkgTokenSpaceGuid.PcdExpression_1 gUefiLessonsPkgTokenSpaceGuid.PcdExpression_2 gUefiLessonsPkgTokenSpaceGuid.PcdExpression_3 ``` And if you build and look at the `AutoGen.h` you'll see that values are calculated correctly: ``` ... #define _PCD_VALUE_PcdExpression 4294967295U // =0xffffffff ... #define _PCD_VALUE_PcdExpression_1 65331U // =0xff33 ... #define _PCD_VALUE_PcdExpression_2 1048576U // =0x100000 ... #define _PCD_VALUE_PcdExpression_3 1U ... ``` # `VOID*` PCD data type Besides the simple types `UINT8`/`UINT16`/`UINT32`/`UINT64`/`BOOLEAN` EDKII allows to use `VOID*` type. Let's look at the ways how it can be used. ## String PCDs Add these PCDs to the `UefiLessonsPkg/UefiLessonsPkg.dec`: ``` [PcdsFixedAtBuild] ... gUefiLessonsPkgTokenSpaceGuid.PcdAsciiStr|"hello"|VOID*|0xB29914B5 gUefiLessonsPkgTokenSpaceGuid.PcdUCS2Str|L"hello"|VOID*|0xF22124E5 ``` In case you didn't notice the difference is in that in the first case the value provided as `"<...>"`, and in the second case as `L"<...>"`. Now add the PCDs to the `UefiLessonsPkg/PCDLesson/PCDLesson.inf`: ``` [FixedPcd] ... gUefiLessonsPkgTokenSpaceGuid.PcdAsciiStr gUefiLessonsPkgTokenSpaceGuid.PcdUCS2Str ``` Build and investigate generated AutoGen files: - `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/AutoGen.h`: ``` #define _PCD_TOKEN_PcdAsciiStr 0U #define _PCD_VALUE_PcdAsciiStr _gPcd_FixedAtBuild_PcdAsciiStr extern const UINT8 _gPcd_FixedAtBuild_PcdAsciiStr[6]; #define _PCD_GET_MODE_PTR_PcdAsciiStr _gPcd_FixedAtBuild_PcdAsciiStr #define _PCD_SIZE_PcdAsciiStr 6 #define _PCD_GET_MODE_SIZE_PcdAsciiStr _PCD_SIZE_PcdAsciiStr //#define _PCD_SET_MODE_PTR_PcdAsciiStr ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD #define _PCD_TOKEN_PcdUCS2Str 0U #define _PCD_VALUE_PcdUCS2Str _gPcd_FixedAtBuild_PcdUCS2Str extern const UINT16 _gPcd_FixedAtBuild_PcdUCS2Str[6]; #define _PCD_GET_MODE_PTR_PcdUCS2Str _gPcd_FixedAtBuild_PcdUCS2Str #define _PCD_SIZE_PcdUCS2Str 12 #define _PCD_GET_MODE_SIZE_PcdUCS2Str _PCD_SIZE_PcdUCS2Str //#define _PCD_SET_MODE_PTR_PcdUCS2Str ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD ``` - `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/AutoGen.c`: ``` GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdAsciiStr[6] = {104, 101, 108, 108, 111, 0 }; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdAsciiStr = 6; GLOBAL_REMOVE_IF_UNREFERENCED const UINT16 _gPcd_FixedAtBuild_PcdUCS2Str[6] = {104, 101, 108, 108, 111, 0 }; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdUCS2Str = 12; ``` As you can see the array for the `PcdAsciiStr` consists from the UINT8 elements, while the `PcdUCS2Str` array consists of `UINT16` elements. Another important difference is that the constants for the string sizes in bytes are generated (effectively it is the `sizeof()` value). These values are calculated dynamically by the build system. To get the `VOID*` fixed type PCD value you can use `FixedPcdGetPtr`/`PcdGetPtr` APIs and to get its size in bytes you can use `FixedPcdGetSize`/`PcdGetSize` APIs: ```cpp Print(L"PcdAsciiStr=%a\n", FixedPcdGetPtr(PcdAsciiStr)); Print(L"PcdAsciiStrSize=%d\n", FixedPcdGetSize(PcdAsciiStr)); Print(L"PcdUCS2Str=%s\n", PcdGetPtr(PcdUCS2Str)); Print(L"PcdUCS2StrSize=%d\n", PcdGetSize(PcdUCS2Str)); ``` Here are results: ``` FS0:\> PCDLesson.efi ... PcdAsciiStr=hello PcdAsciiStrSize=6 PcdUCS2Str=hello PcdUCS2StrSize=12 ``` If you want to understand preprocessor substitutions, you can unravel its logic like we did before using the defines from the [https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PcdLib.h](https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PcdLib.h): ```cpp #define FixedPcdGetPtr(TokenName) ((VOID *)_PCD_VALUE_##TokenName) #define FixedPcdGetSize(TokenName) _PCD_SIZE_##TokenName #define PcdGetPtr(TokenName) _PCD_GET_MODE_PTR_##TokenName #define PcdGetSize(TokenName) _PCD_GET_MODE_SIZE_##TokenName ``` In our example we've used `"..."/L"..."` syntax for string initialization, but it is also possible to use `'...'/L'...'` syntax. ## Byte Array PCDs The more general usage for `VOID*` is byte arrays. As an example add this PCD to the `UefiLessonsPkg/UefiLessonsPkg.dec`: ``` [PcdsFixedAtBuild] ... gUefiLessonsPkgTokenSpaceGuid.PcdArray|{0xA5, 0xA6, 0xA7}|VOID*|0xD5DB9A27 ``` Populate it to the `UefiLessonsPkg/PCDLesson/PCDLesson.inf`: ``` [FixedPcd] ... gUefiLessonsPkgTokenSpaceGuid.PcdArray ``` And look at the AutoGen files: - `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/AutoGen.h`: ``` #define _PCD_TOKEN_PcdArray 0U #define _PCD_VALUE_PcdArray (VOID *)_gPcd_FixedAtBuild_PcdArray extern const UINT8 _gPcd_FixedAtBuild_PcdArray[3]; #define _PCD_GET_MODE_PTR_PcdArray (VOID *)_gPcd_FixedAtBuild_PcdArray #define _PCD_SIZE_PcdArray 3 #define _PCD_GET_MODE_SIZE_PcdArray _PCD_SIZE_PcdArray //#define _PCD_SET_MODE_PTR_PcdArray ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD ``` - `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/AutoGen.c`: ``` GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdArray[3] = {0xA5, 0xA6, 0xA7}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdArray = 3; ``` Here is a one way of how we can print elements of our array. We need the `(UINT8*)` cast as the data is cast to `(VOID *)` in `AutoGen.h`. ``` for (UINTN i=0; i PCDLesson.efi ... PcdArray[0]=0xA5 PcdArray[1]=0xA6 PcdArray[2]=0xA7 ``` ## GUID PCDs With the byte array initialization syntax you can initialize any custom structure, as soon as you understand its type. Let's take `EFI_GUID` (`=GUID`) structure: ``` typedef struct { UINT32 Data1; UINT16 Data2; UINT16 Data3; UINT8 Data4[8]; } GUID; typedef GUID EFI_GUID; ``` If you want to encode GUID "f1740707-691d-4203-bfab-99e132fa4166" you can do it like this: ``` [PcdsFixedAtBuild] ... gUefiLessonsPkgTokenSpaceGuid.PcdGuidInBytes|{0x07, 0x07, 0x74, 0xF1, 0x1D, 0x69, 0x03, 0x42, 0xBF, 0xAB, 0x99, 0xE1, 0x32, 0xFA, 0x41, 0x66}|VOID*|0xB9E0CDC0 ``` So as you can see you need to reshuffle some bytes to get a correct representation. This is necessary because x86 is little-endian, and in such systems bytes of a number are placed in memory from highest to lowest. So this is the thing that you need to keep in mind when you would encode byte array initializations for you custom structures. As GUIDs are excessively used in UEFI code, EDK2 has a helper syntax for GUID PCD initialization. Let's define another PCD via this helper syntax: ``` [PcdsFixedAtBuild] ... gUefiLessonsPkgTokenSpaceGuid.PcdGuid|{GUID("f1740707-691d-4203-bfab-99e132fa4166")}|VOID*|0x7F2066F7 ``` Now populate both values to INF: ``` [FixedPcd] ... gUefiLessonsPkgTokenSpaceGuid.PcdGuidInBytes gUefiLessonsPkgTokenSpaceGuid.PcdGuid ``` Now build and look at the AutoGen files: - `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/AutoGen.h` ``` #define _PCD_TOKEN_PcdGuidInBytes 0U #define _PCD_VALUE_PcdGuidInBytes (VOID *)_gPcd_FixedAtBuild_PcdGuidInBytes extern const UINT8 _gPcd_FixedAtBuild_PcdGuidInBytes[16]; #define _PCD_GET_MODE_PTR_PcdGuidInBytes (VOID *)_gPcd_FixedAtBuild_PcdGuidInBytes #define _PCD_SIZE_PcdGuidInBytes 16 #define _PCD_GET_MODE_SIZE_PcdGuidInBytes _PCD_SIZE_PcdGuidInBytes //#define _PCD_SET_MODE_PTR_PcdGuidInBytes ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD #define _PCD_TOKEN_PcdGuid 0U #define _PCD_VALUE_PcdGuid (VOID *)_gPcd_FixedAtBuild_PcdGuid extern const UINT8 _gPcd_FixedAtBuild_PcdGuid[16]; #define _PCD_GET_MODE_PTR_PcdGuid (VOID *)_gPcd_FixedAtBuild_PcdGuid #define _PCD_SIZE_PcdGuid 16 #define _PCD_GET_MODE_SIZE_PcdGuid _PCD_SIZE_PcdGuid //#define _PCD_SET_MODE_PTR_PcdGuid ASSERT(FALSE) // It is not allowed to set value for a FIXED_AT_BUILD PCD ``` - `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PCDLesson/PCDLesson/DEBUG/AutoGen.c` ``` GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdGuidInBytes[16] = {0x07, 0x07, 0x74, 0xF1, 0x1D, 0x69, 0x03, 0x42, 0xBF, 0xAB, 0x99, 0xE1, 0x32, 0xFA, 0x41, 0x66}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdGuidInBytes = 16; GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdGuid[16] = {0x07, 0x07, 0x74, 0xF1, 0x1D, 0x69, 0x03, 0x42, 0xBF, 0xAB, 0x99, 0xE1, 0x32, 0xFA, 0x41, 0x66}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdGuid = 16; ``` As you can see in the output, the PCD encoding and initialization are the same in both cases. If we want to use GUIDs in code, we still need not forget to cast them from `(VOID *)` to `(EFI_GUID*)`: ```cpp Print(L"PcdGuidInBytes=%g\n", *(EFI_GUID*)FixedPcdGetPtr(PcdGuidInBytes)); Print(L"PcdGuid=%g\n", *(EFI_GUID*)FixedPcdGetPtr(PcdGuid)); ``` The result of both `Print` statements would be the same: ``` FS0:\> PCDLesson.efi ... PcdGuidInBytes=F1740707-691D-4203-BFAB-99E132FA4166 PcdGuid=F1740707-691D-4203-BFAB-99E132FA4166 ``` There are two more more methods how you can initialize PCD containing GUID. You can either use another GUID for initialization or standard C syntax for `EFI_GUID` structure initialization: ``` [PcdsFixedAtBuild] ... gUefiLessonsPkgTokenSpaceGuid.PcdGuidByPCD|{GUID(gUefiLessonsPkgTokenSpaceGuid)}|VOID*|0x0860CCD5 gUefiLessonsPkgTokenSpaceGuid.PcdGuidByEfiGuid|{GUID({0x150cab53, 0xad47, 0x4385, {0xb5, 0xdd, 0xbc, 0xfc, 0x76, 0xba, 0xca, 0xf0}})}|VOID*|0x613506D5 ``` This would `AutoGen.c`: ```cpp GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdGuidByPCD[16] = {0x53, 0xAB, 0x0C, 0x15, 0x47, 0xAD, 0x85, 0x43, 0xB5, 0xDD, 0xBC, 0xFC, 0x76, 0xBA, 0xCA, 0xF0}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdGuidByPCD = 16; GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdGuidByEfiGuid[16] = {0x53, 0xAB, 0x0C, 0x15, 0x47, 0xAD, 0x85, 0x43, 0xB5, 0xDD, 0xBC, 0xFC, 0x76, 0xBA, 0xCA, 0xF0}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdGuidByEfiGuid = 16; ``` ## `DEVICE_PATH` Besides the `GUID(...)` helper, EDKII also has `DEVICE_PATH(...)` helper, to initialize another special UEFI structure - device path. We'll investigate this structure another time. But for an example, the paths that are printed at the start of UEFI shell are text representation of UEFI device paths: ``` UEFI Interactive Shell v2.2 EDK II UEFI v2.70 (EDK II, 0x00010000) Mapping table FS0: Alias(s):HD0a1:;BLK1: PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1) BLK0: Alias(s): PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0) BLK2: Alias(s): PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0) Press ESC in 5 seconds to skip startup.nsh or any other key to continue. ``` Let's use one of them for PCD initialization: ``` [PcdsFixedAtBuild] ... gUefiLessonsPkgTokenSpaceGuid.PcdDevicePath|{DEVICE_PATH("PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)")}|VOID*|0xC56EE1E2 ``` This would produce the following array in the `AutoGen.c`: ``` GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdDevicePath[30] = {0x02,0x01,0x0c,0x00,0xd0,0x41,0x03,0x0a,0x00,0x00,0x00,0x00,0x01,0x01,0x06,0x00,0x01,0x01,0x03,0x01,0x08,0x00,0x00,0x00,0x00,0x00,0x7f,0xff,0x04,0x00}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdDevicePath = 30; ``` To verify that the array are correct, let's print created device path in our program. For this we need to include `DevicePathLib.h` header: ```cpp #include ``` And use `ConvertDevicePathToText` function: ```cpp Print(L"PcdDevicePath: %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) FixedPcdGetPtr(PcdDevicePath), FALSE, FALSE)); ``` Here is the output: ``` FS0:\> PCDLesson.efi ... PcdDevicePath: PciRoot(0x0)/Pci(0x1,0x1)/Ata(Primary,Master,0x0) ``` ## Using integer casts It is possible to initialize `VOID*` array using `UINT8(...)`, `UINT16(...)`, `UINT32(...)`, `UINT64(...)` cast helpers: ``` [PcdsFixedAtBuild] ... gUefiLessonsPkgTokenSpaceGuid.PcdIntCasts|{UINT16(0x1122), UINT32(0x33445566), UINT8(0x77), UINT64(0x8899aabbccddeeff)}|VOID*|0x647456A6 ``` If you look at the `AutoGen.c` you'll see that array is initialized with respect to little-endian architecture: ``` GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdIntCasts[15] = {0x22, 0x11, 0x66, 0x55, 0x44, 0x33, 0x77, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdIntCasts = 15; ``` ## `LABEL` and `OFFSET_OF` It is possible to assign labels to some elemets and get offsets of these elements via `LABEL(...)/OFFSET_OF(...)` syntax. Example: ``` [PcdsFixedAtBuild] ... UefiLessonsPkgTokenSpaceGuid.PcdWithLabels|{ 0x0A, 0x0B, OFFSET_OF(End), 0x0C, LABEL(Start) 0x0D, LABEL(End) 0x0E, 0x0F, OFFSET_OF(Start) }|VOID*|0xD91A8BF6 ``` This will give you this in `AutoGen.c`: ``` GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdWithLabels[8] = {0x0A, 0x0B, 0x05, 0x0C, 0x0D, 0x0E, 0x0F, 0x04}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdWithLabels = 8; ``` ## Combined array initialization It is possible to combine different methods of byte array initialization: ``` gUefiLessonsPkgTokenSpaceGuid.PcdArrayExt|{0x11, UINT16(0x2233), UINT32(0x44556677), L"hello", "world!", GUID("09b9b358-70bd-421e-bafb-4f97e2ac7d44")}|VOID*|0x7200C5DF ``` This will give you: ``` GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdArrayExt[42] = {0x11, 0x33, 0x22, 0x77, 0x66, 0x55, 0x44, 0x68, 0x00, 0x65, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21, 0x0 0, 0x58, 0xB3, 0xB9, 0x09, 0xBD, 0x70, 0x1E, 0x42, 0xBA, 0xFB, 0x4F, 0x97, 0xE2, 0xAC, 0x7D, 0x44}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdArrayExt = 42; ``` # Custom types It is possible to create custom types for you PCDs. For example create this file `UefiLessonsPkg/Include/CustomPcdTypes.h`: ``` #ifndef CUSTOM_PCD_TYPES_H #define CUSTOM_PCD_TYPES_H typedef struct { EFI_GUID Guid; CHAR16 Name[6]; } InnerCustomStruct; typedef struct { UINT8 Val8; UINT32 Val32[2]; InnerCustomStruct ValStruct; union { struct { UINT8 Field1:1; UINT8 Field2:4; UINT8 Filed3:3; } BitFields; UINT8 Byte; } ValUnion; } CustomStruct; #endif ``` Add the `Include` folder to the DEC file: ``` [Includes] Include ``` You can use the created `CustomStruct` structure type and initialize its values via this sytnax: ``` gUefiLessonsPkgTokenSpaceGuid.PcdCustomStruct|{0}|CustomStruct|0x535D4CB5 { UefiLessonsPkg/UefiLessonsPkg.dec CustomPcdTypes.h } gUefiLessonsPkgTokenSpaceGuid.PcdCustomStruct.Val8|0x11 gUefiLessonsPkgTokenSpaceGuid.PcdCustomStruct.Val32[0]|0x22334455 gUefiLessonsPkgTokenSpaceGuid.PcdCustomStruct.Val32[1]|0x66778899 gUefiLessonsPkgTokenSpaceGuid.PcdCustomStruct.ValStruct.Guid|{GUID("f1740707-691d-4203-bfab-99e132fa4166")} gUefiLessonsPkgTokenSpaceGuid.PcdCustomStruct.ValStruct.Name|L'Hello' gUefiLessonsPkgTokenSpaceGuid.PcdCustomStruct.ValUnion.BitFields.Field2|0xF ``` `AutoGen.c` will be created with the following data: ``` GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdCustomStruct[44] = {0x11,0x00,0x00,0x00,0x55,0x44,0x33,0x22,0x99,0x88,0x77,0x66,0x07,0x07,0x74,0xf1,0x1d,0x69,0x03,0x42,0xbf,0xab,0x99,0xe1,0x32,0xfa,0x41,0x66,0x48,0x00,0x65,0x00,0x6c,0x00,0x6c,0x00,0x6f,0x00,0x00,0x00,0x1e,0x00,0x00,0x00}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdCustomStruct = 44; ``` If you group you fields, you'll see that all of them were initialized as intendend: ``` { 0x11, // UINT8 Val8 0x00,0x00,0x00, // alignment 0x55,0x44,0x33,0x22, // UINT32 Val32[0] 0x99,0x88,0x77,0x66, // UINT32 Val32[1] 0x07,0x07,0x74,0xf1,0x1d,0x69,0x03,0x42,0xbf,0xab,0x99,0xe1,0x32,0xfa,0x41,0x66, // InnerCustomStruct.Guid 0x48,0x00,0x65,0x00,0x6c,0x00,0x6c,0x00,0x6f,0x00,0x00,0x00, // InnerCustomStruct.Name 0x1e, // ValUnion 0x00,0x00,0x00 // alignment } ``` If a field-by-filed initialization seem too long, you can use in-place C style array initialization with the help of a special `CODE(...)` syntax. But in this case you can't use helpers like `GUID(...)` or `L'...'` initialization. So the same initialization for our structure would look like this: ``` gUefiLessonsPkgTokenSpaceGuid.PcdCustomStruct_1|{CODE( { 0x11, {0x22334455, 0x66778899}, { {0xf1740707, 0x691d, 0x4203, {0xbf, 0xab, 0x99, 0xe1, 0x32, 0xfa, 0x41, 0x66}}, {0x0048, 0x0065, 0x006c, 0x006c, 0x006f, 0x0000} }, {{0x0, 0xf, 0x0}} } )}|CustomStruct|0xC1D6B9A7 { UefiLessonsPkg/UefiLessonsPkg.dec CustomPcdTypes.h } ``` You can verify that the result is the same data, if you look at the `AutoGen.c` file: ``` GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdCustomStruct_1[44] = {0x11,0x00,0x00,0x00,0x55,0x44,0x33,0x22,0x99,0x88,0x77,0x66,0x07,0x07,0x74,0xf1,0x1d,0x69,0x03,0x42,0xbf,0xab,0x99,0xe1,0x32,0xfa,0x41,0x66,0x48,0x00,0x65,0x00,0x6c,0x00,0x6c,0x00,0x6f,0x00,0x00,0x00,0x1e,0x00,0x00,0x00}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdCustomStruct_1 = 44; ``` # Array with fixed sizes It is possible to fix PCD array size. Keep in mind that if you use arrays for types with sizes more that 1 byte like `UINT32[3]`, you need to cast every initialization value to the type`. To avoid this it is possible to use special `CODE(...)` syntax: ``` [PcdsFixedAtBuild] ... gUefiLessonsPkgTokenSpaceGuid.PcdArrayWithFixedSize|{0x0}|UINT8[12]|0x4C4CB9A3 gUefiLessonsPkgTokenSpaceGuid.PcdArrayWithFixedSize_1|{0x0}|UINT32[3]|0x285DAD21 gUefiLessonsPkgTokenSpaceGuid.PcdArrayWithFixedSize_2|{UINT32(0x11223344), UINT32(0x55667788), UINT32(0x99aabbcc)}|UINT32[3]|0x25D6ED26 gUefiLessonsPkgTokenSpaceGuid.PcdArrayWithFixedSize_3|{CODE({0x11223344, 0x55667788, 0x99aabbcc})}|UINT32[3]|0xE5BC424D ``` This will give you this in `AutoGen.c`: ``` GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdArrayWithFixedSize[8] = {0xee,0xff,0x00,0x00,0x00,0x00,0x00,0x00}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdArrayWithFixedSize = 8; GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdArrayWithFixedSize_1[12] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdArrayWithFixedSize_1 = 12; GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdArrayWithFixedSize_2[12] = {0x44,0x33,0x22,0x11,0x88,0x77,0x66,0x55,0xcc,0xbb,0xaa,0x99}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdArrayWithFixedSize_2 = 12; GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 _gPcd_FixedAtBuild_PcdArrayWithFixedSize_3[12] = {0x44,0x33,0x22,0x11,0x88,0x77,0x66,0x55,0xcc,0xbb,0xaa,0x99}; GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdArrayWithFixedSize_3 = 12; ``` You can also create fixed size arrays for you custom types: ``` gUefiLessonsPkgTokenSpaceGuid.PcdArrayWithFixedSize_4|{0x0}|CustomStruct[2]|0x0D00EE44 { UefiLessonsPkg/UefiLessonsPkg.dec CustomPcdTypes.h } ``` Here we don't do any field initialization, but for the proof of syntax you can look at the `AutoGen.c` and verify that the size of the final data array is twice of the usual one for our structure: ``` GLOBAL_REMOVE_IF_UNREFERENCED const UINTN _gPcd_FixedAtBuild_Size_PcdArrayWithFixedSize_4 = 88; ``` # `PcdValueInit` One interesting observation. When you use PCD with custom types, or fixed size arrays, the build system would create `PcdValueInit` folder in your `Build` directory. This directory would contain a special C program `PcdValueInit` that the build system will use to get the data for the `AutoGen.c` file: ``` $ find ./Build/UefiLessonsPkg/RELEASE_GCC5/PcdValueInit/ ./Build/UefiLessonsPkg/RELEASE_GCC5/PcdValueInit/ ./Build/UefiLessonsPkg/RELEASE_GCC5/PcdValueInit/PcdValueCommon.c ./Build/UefiLessonsPkg/RELEASE_GCC5/PcdValueInit/PcdValueInit.o ./Build/UefiLessonsPkg/RELEASE_GCC5/PcdValueInit/PcdValueInit.c ./Build/UefiLessonsPkg/RELEASE_GCC5/PcdValueInit/PcdValueInit ./Build/UefiLessonsPkg/RELEASE_GCC5/PcdValueInit/Input.txt ./Build/UefiLessonsPkg/RELEASE_GCC5/PcdValueInit/PcdValueInit.d ./Build/UefiLessonsPkg/RELEASE_GCC5/PcdValueInit/PcdValueCommon.d ./Build/UefiLessonsPkg/RELEASE_GCC5/PcdValueInit/Output.txt ./Build/UefiLessonsPkg/RELEASE_GCC5/PcdValueInit/PcdValueCommon.o ./Build/UefiLessonsPkg/RELEASE_GCC5/PcdValueInit/Makefile ``` You can check the help message of the program: ``` $ ./Build/UefiLessonsPkg/RELEASE_GCC5/PcdValueInit/PcdValueInit -h Usage: -i -o optional arguments: -h, --help Show this help message and exit -i INPUT_FILENAME, --input INPUT_FILENAME PCD Database Input file name -o OUTPUT_FILENAME, --output OUTPUT_FILENAME PCD Database Output file name ``` From this you can guess that this programm creates `Output.txt` file from the `Input.txt` file in the same directory. If you curious about the build system internals you can check these `*.txt` files or check the program sources. # Links - [https://edk2-docs.gitbook.io/edk-ii-pcd-specification/](https://edk2-docs.gitbook.io/edk-ii-pcd-specification/) - [https://edk2-docs.gitbook.io/edk-ii-dec-specification/2_dec_file_overview/210_pcd_usage](https://edk2-docs.gitbook.io/edk-ii-dec-specification/2_dec_file_overview/210_pcd_usage)