Skip to content

Latest commit

 

History

History

Lesson_20

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

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

Other simple PCD types

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

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:

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.

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<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

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*):

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;

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:

#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)

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 {
  <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;

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 {
  <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;

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 <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.

Links