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:
<TokenSpaceGuidCName>.<PcdCName>|<DefaultValue>|<DatumType>|<Token>
<TokenSpaceGuidCName>
is a GUID value, <Token>
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 g<PackageName>TokenSpaceGuid
, so add this to our UefiLessonsPkg/UefiLessonsPkg.dec
:
[Guids]
...
gUefiLessonsPkgTokenSpaceGuid = {0x150cab53, 0xad47, 0x4385, {0xb5, 0xdd, 0xbc, 0xfc, 0x76, 0xba, 0xca, 0xf0}}
As for the <Token>
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 <Library/PcdLib.h>
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:
#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
In the example above we've used UINT8
as a <DatumType>
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
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
...
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.
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:
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:
#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.
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<FixedPcdGetSize(PcdArray); i++) {
Print(L"PcdArray[%d]=0x%02x\n", i, ((UINT8*)FixedPcdGetPtr(PcdArray))[i]);
}
Verify the output:
FS0:\> PCDLesson.efi
...
PcdArray[0]=0xA5
PcdArray[1]=0xA6
PcdArray[2]=0xA7
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*)
:
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
:
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;
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:
#include <Library/DevicePathLib.h>
And use ConvertDevicePathToText
function:
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)
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;
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;
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;
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 {
<Packages>
UefiLessonsPkg/UefiLessonsPkg.dec
<HeaderFiles>
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 {
<Packages>
UefiLessonsPkg/UefiLessonsPkg.dec
<HeaderFiles>
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;
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 {
<Packages>
UefiLessonsPkg/UefiLessonsPkg.dec
<HeaderFiles>
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;
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 <input_file> -o <output_file>
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.