From 4a40f4cc0e45de28898cdad1b605cc4ca4be2710 Mon Sep 17 00:00:00 2001 From: Konstantin Aladyshev Date: Wed, 31 Aug 2022 16:26:50 +0300 Subject: Finish SKU lesson Signed-off-by: Konstantin Aladyshev --- Lessons_uncategorized/Lesson_SKU/README.md | 427 ++++++++++++++++++++- .../Lesson_SKU/UefiLessonsPkg/SetSku/SetSku.c | 72 ++++ .../Lesson_SKU/UefiLessonsPkg/SetSku/SetSku.inf | 17 + 3 files changed, 515 insertions(+), 1 deletion(-) create mode 100644 Lessons_uncategorized/Lesson_SKU/UefiLessonsPkg/SetSku/SetSku.c create mode 100644 Lessons_uncategorized/Lesson_SKU/UefiLessonsPkg/SetSku/SetSku.inf (limited to 'Lessons_uncategorized/Lesson_SKU') diff --git a/Lessons_uncategorized/Lesson_SKU/README.md b/Lessons_uncategorized/Lesson_SKU/README.md index 01884a4..38557ee 100644 --- a/Lessons_uncategorized/Lesson_SKU/README.md +++ b/Lessons_uncategorized/Lesson_SKU/README.md @@ -223,14 +223,63 @@ Value: So the point here is that information for every SKU override is encoded in the PCD Database. +In the nutshell each Sku override is represented as an array of `PCD_DATA_DELTA` structures: +``` +typedef struct { + UINT32 Offset : 24; + UINT32 Value : 8; +} PCD_DATA_DELTA; +``` +Each of these structures mean "change PCD Database byte at the `PCD_DATA_DELTA.Offset` to the `PCD_DATA_DELTA.Value`". So it is 4 bytes record to change 1 byte of the PCD Database. + +# Changing SKU at runtime + +The whole point of this part of SKU functionality is to change group of PCDs at runtime. So let's investigate how SKU change affect PCD values. -Check PI specification: +We need to write an application that could change current SKU value. + +For this we would need a function from the `EFI_PCD_PROTOCOL` defined in the PI specification: ``` +EFI_PCD_PROTOCOL + +Summary: +A platform database that contains a variety of current platform settings or directives that can be accessed by a driver or application. + +GUID: + #define EFI_PCD_PROTOCOL_GUID \ { 0x13a3f0f6, 0x264a, 0x3ef0, \ { 0xf2, 0xe0, 0xde, 0xc5, 0x12, 0x34, 0x2f, 0x34 } } + + +Protocol Interface Structure: + +typedef struct _EFI_PCD_PROTOCOL { + EFI_PCD_PROTOCOL_SET_SKU SetSku; + EFI_PCD_PROTOCOL_GET_8 Get8; + EFI_PCD_PROTOCOL_GET_16 Get16; + EFI_PCD_PROTOCOL_GET_32 Get32; + EFI_PCD_PROTOCOL_GET_64 Get64; + EFI_PCD_PROTOCOL_GET_POINTER GetPtr; + EFI_PCD_PROTOCOL_GET_BOOLEAN GetBool; + EFI_PCD_PROTOCOL_GET_SIZE GetSize; + EFI_PCD_PROTOCOL_SET_8 Set8; + EFI_PCD_PROTOCOL_SET_16 Set16; + EFI_PCD_PROTOCOL_SET_32 Set32; + EFI_PCD_PROTOCOL_SET_64 Set64; + EFI_PCD_PROTOCOL_SET_POINTER SetPtr; + EFI_PCD_PROTOCOL_SET_BOOLEAN SetBool; + EFI_PCD_PROTOCOL_CALLBACK_ON_SET CallbackOnSet; + EFI_PCD_PROTOCOL_CANCEL_CALLBACK CancelCallback; + EFI_PCD_PROTOCOL_GET_NEXT_TOKEN GetNextToken; + EFI_PCD_PROTOCOL_GET_NEXT_TOKEN_SPACE GetNextTokenSpace; +} EFI_PCD_PROTOCOL; ``` +This is the same protocol that is implicitly used in library calls when you access Dynamic and DynamicEx PCDs. + +But right now we are interested in the `SetSku` function: +``` EFI_PCD_PROTOCOL.SetSku () Summary: @@ -252,6 +301,273 @@ For each item (token), the database can hold a single value that applies to all The SKU Id of zero is reserved as a default. For tokens that are not SKU enabled, the system ignores any set SKU Id and works with the single value for that token. For SKU-enabled tokens, the system will use the SKU Id set by the last call to SetSku(). If no SKU Id is set or the currently set SKU Id isn’t valid for the specified token, the system uses the default SKU Id. If the system attempts to use the default SKU Id and no value has been set for that Id, the results are unpredictable. ``` +To get the current SkuId value we would use another protocol - `EFI_GET_PCD_INFO_PROTOCOL`: +``` +EFI_GET_PCD_INFO_PROTOCOL + +Summary: +The protocol that provides additional information about items that reside in the PCD database. + +GUID: +#define EFI_GET_PCD_INFO_PROTOCOL_GUID \ + { 0xfd0f4478, 0xefd, 0x461d, \ + { 0xba, 0x2d, 0xe5, 0x8c, 0x45, 0xfd, 0x5f, 0x5e } } + + +Protocol Interface Structure: + +typedef struct _EFI_GET_PCD_INFO_PROTOCOL { + EFI_GET_PCD_INFO_PROTOCOL_GET_INFO GetInfo; + EFI_GET_PCD_INFO_PROTOCOL_GET_SKU GetSku; +} EFI_GET_PCD_INFO_PROTOCOL; +``` + +Particularly its function `GetSku`: +``` +EFI_GET_PCD_INFO_PROTOCOL.GetSku () + +Summary: +Retrieve the currently set SKU Id. + +Prototype: +typedef +UINTN +(EFIAPI *EFI_GET_PCD_INFO_PROTOCOL_GET_SKU) ( + VOID + ); + +Description: +GetSku() returns the currently set SKU Id. If the platform has not set at a SKU Id, then the default SKU Id value of 0 is returned. If the platform has set a SKU Id, then the currently set SKU Id is returned. +``` + +Now let's write a shell application `SetSku` that sets SkuId based on the input argument: +```cpp +#include +#include + +#include +#include +#include + + +VOID Usage() +{ + Print(L"Program to set Sku number\n\n"); + Print(L" Usage: SetSku \n"); +} + +INTN +EFIAPI +ShellAppMain ( + IN UINTN Argc, + IN CHAR16 **Argv + ) +{ + EFI_STATUS Status; + + if (Argc != 2) { + Usage(); + return EFI_INVALID_PARAMETER; + } + + UINTN SkuNumber; + RETURN_STATUS ReturnStatus; + ReturnStatus = StrHexToUintnS(Argv[1], NULL, &SkuNumber); + if (RETURN_ERROR(ReturnStatus)) { + Print(L"Error! Can't convert SkuId string to number\n"); + return EFI_INVALID_PARAMETER; + } + + EFI_PCD_PROTOCOL* pcdProtocol; + Status = gBS->LocateProtocol(&gEfiPcdProtocolGuid, + NULL, + (VOID **)&pcdProtocol); + if (EFI_ERROR(Status)) { + Print(L"Error! Can't find EFI_PCD_PROTOCOL\n"); + return EFI_UNSUPPORTED; + } + + EFI_GET_PCD_INFO_PROTOCOL* getPcdInfoProtocol; + Status = gBS->LocateProtocol(&gEfiGetPcdInfoProtocolGuid, + NULL, + (VOID **)&getPcdInfoProtocol); + if (EFI_ERROR(Status)) { + Print(L"Error! Can't find EFI_GET_PCD_INFO_PROTOCOL\n"); + return EFI_UNSUPPORTED; + } + + + pcdProtocol->SetSku(SkuNumber); + + UINTN SkuId = getPcdInfoProtocol->GetSku(); + if (SkuId != SkuNumber) { + Print(L"Failed to change SkuId to 0x%lx, SkuId is still 0x%lx\n", SkuNumber, SkuId); + return EFI_UNSUPPORTED; + } + + Print(L"Sku is changed successfully to 0x%lx\n", SkuNumber); + + return EFI_SUCCESS; +} +``` + +The program is very simple, so I hope it doesn't need any comments. + +Let's build it and use under UEFI shell. + +First with a help of `DumpDynPcd` application check initial value for our PCD: +``` +FS0:\> DumpDynPcd.efi +... +150CAB53-AD47-4385-B5DD-BCFC76BACAF0 + Token = 0xAF35F3B2 - Type = UINT32:DYNAMICEX - Size = 0x4 - Value = 0xBABEBABE +``` +Now use `SetSku` to change SkuId: +``` +FS0:\> SetSku.efi 0x55 +Sku is changed successfully to 0x55 +``` +And verify PCD value again: +``` +FS0:\> DumpDynPcd.efi +... +150CAB53-AD47-4385-B5DD-BCFC76BACAF0 + Token = 0xAF35F3B2 - Type = UINT32:DYNAMICEX - Size = 0x4 - Value = 0x11111111 +``` +You can see that the PCD value was affected by the SkuId set operation. This is the main point of SkuId change. + +Usually the firmware on boot checks some hardware configuration which defines different board variants (for example hardware configuration can be encoded as the input value on some GPIO pins). Based on the decoded hardware configuration firmware sets SkuId, thus loading a whole new set of PCDs that belong to this particular board variant. This way it is possible to have common firmware for different boards. + +# SkuId can be set only once + +Let's get back to the SkuId mechanics investigation. First point is that you can set SkuId only once at boot. If you try to execute `SetSku` application again it would fail: +``` +FS0:\> SetSku.efi 0x66 +Failed to change SkuId to 0x66, SkuId is still 0x55 +``` +And the PCD value wouldn't change to the new Sku override: +``` +FS0:\> DumpDynPcd.efi +... +150CAB53-AD47-4385-B5DD-BCFC76BACAF0 + Token = 0xAF35F3B2 - Type = UINT32:DYNAMICEX - Size = 0x4 - Value = 0x11111111 +``` +Initial call to our application have worked just because OVMF firmware didn't perform `EFI_PCD_PROTOCOL->SetSku` on boot. + +If you want to see how this check is done in EDKII code check [https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/PCD/Dxe/Pcd.c](https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/PCD/Dxe/Pcd.c): +```cpp +VOID +EFIAPI +DxePcdSetSku ( + IN UINTN SkuId + ) +{ +... + if (mPcdDatabase.DxeDb->SystemSkuId != (SKU_ID)0) { + DEBUG ((DEBUG_ERROR, "PcdDxe - The SKU Id could be changed only once.")); + DEBUG (( + DEBUG_ERROR, + "PcdDxe - The SKU Id was set to 0x%lx already, it could not be set to 0x%lx any more.", + mPcdDatabase.DxeDb->SystemSkuId, + (SKU_ID)SkuId + )); + ASSERT (FALSE); + return; + } +... +} +``` + +You can verify that it is still possible to change SkuId to the 0x66 value if you do it first. Restart QEMU and execute these operations: +``` +FS0:\> DumpDynPcd.efi +... +150CAB53-AD47-4385-B5DD-BCFC76BACAF0 + Token = 0xAF35F3B2 - Type = UINT32:DYNAMICEX - Size = 0x4 - Value = 0xBABEBABE + +FS0:\> SetSku.efi 0x66 +Sku is changed successfully to 0x66 + +FS0:\> DumpDynPcd.efi +... +150CAB53-AD47-4385-B5DD-BCFC76BACAF0 + Token = 0xAF35F3B2 - Type = UINT32:DYNAMICEX - Size = 0x4 - Value = 0x22222222 +``` + +So as you can see, the PCD value was successfully changed to the override of the SkuId 0x66. + +# Incorrect SkuId + +Restart QEMU and try to set SkuId outside of the available ones: +``` +FS0:\> SetSku 0x99 +Failed to change SkuId to 0x99, SkuId is still 0x0 +``` +Here you can see that the SkuId wouldn't be changed if you supply unknown SkuId value. + + +# Changing SkuId after PCD change + +What will happen if we will change PCD value from its default and then change SkuId? + +We can verify this with our `PCDLesson.efi` application as it changes the value for the PCD at runtime: +``` +FS0:\> DumpDynPcd.efi +... +150CAB53-AD47-4385-B5DD-BCFC76BACAF0 + Token = 0xAF35F3B2 - Type = UINT32:DYNAMICEX - Size = 0x4 - Value = 0xBABEBABE + +FS0:\> PCDLesson.efi + +FS0:\> DumpDynPcd.efi +... +150CAB53-AD47-4385-B5DD-BCFC76BACAF0 + Token = 0xAF35F3B2 - Type = UINT32:DYNAMICEX - Size = 0x4 - Value = 0x88888888 + +FS0:\> SetSku.efi 0x55 +Sku is changed successfully to 0x55 + +FS0:\> DumpDynPcd.efi +... +150CAB53-AD47-4385-B5DD-BCFC76BACAF0 + Token = 0xAF35F3B2 - Type = UINT32:DYNAMICEX - Size = 0x4 - Value = 0x11111111 +``` + +As you can see the SkuId change sets any affected PCDs, even if they were changed before. + +# PEI stage + +Our `SetSku` application is supposed to be launched in the DXE stage, so we were using the PCD database protocols for the DXE stage. + +But the PCD database is also used in the PEI stage. So there is another PCD database protocol for the PEI stage - `EFI_PEI_PCD_PPI`: +``` +#define EFI_PEI_PCD_PPI_GUID \ + { 0x1f34d25, 0x4de2, 0x23ad, \ + { 0x3f, 0xf3, 0x36, 0x35, 0x3f, 0xf3, 0x23, 0xf1 } } + +typedef struct { + EFI_PEI_PCD_PPI_SET_SKU SetSku; + EFI_PEI_PCD_PPI_GET_8 Get8; + EFI_PEI_PCD_PPI_GET_16 Get16; + EFI_PEI_PCD_PPI_GET_32 Get32; + EFI_PEI_PCD_PPI_GET_64 Get64; + EFI_PEI_PCD_PPI_GET_POINTER GetPtr; + EFI_PEI_PCD_PPI_GET_BOOLEAN GetBool; + EFI_PEI_PCD_PPI_GET_SIZE GetSize; + EFI_PEI_PCD_PPI_SET_8 Set8; + EFI_PEI_PCD_PPI_SET_16 Set16; + EFI_PEI_PCD_PPI_SET_32 Set32; + EFI_PEI_PCD_PPI_SET_64 Set64; + EFI_PEI_PCD_PPI_SET_POINTER SetPtr; + EFI_PEI_PCD_PPI_SET_BOOLEAN SetBool; + EFI_PEI_PCD_PPI_CALLBACK_ON_SET CallbackOnSet; + EFI_PEI_PCD_PPI_CANCEL_CALLBACK CancelCallback; + EFI_PEI_PCD_PPI_GET_NEXT_TOKEN GetNextToken; + EFI_PEI_PCD_PPI_GET_NEXT_TOKEN_SPACE GetNextTokenSpace; +} EFI_PEI_PCD_PPI; +``` + +The `SetSku` function is similar to the one that we've used: ``` EFI_PEI_PCD_PPI.SetSku () @@ -272,6 +588,115 @@ Description: ... ``` +The same goes to the `EFI_GET_PCD_INFO_PROTOCOL` and its `GetSku` function. Here are their analogs for the PEI stage: +``` +#define EFI_GET_PCD_INFO_PPI_GUID \ + { 0xa60c6b59, 0xe459, 0x425d, \ + { 0x9c, 0x69, 0xb, 0xcc, 0x9c, 0xb2, 0x7d, 0x81 } } + +typedef struct _EFI_GET_PCD_INFO_PPI { + EFI_GET_PCD_INFO_PPI_GET_INFO GetInfo; + EFI_GET_PCD_INFO_PPI_GET_SKU GetSku; +} EFI_GET_PCD_INFO_PPI; +``` +``` +EFI_GET_PCD_INFO_PPI.GetSku () + +Summary: +Retrieve the currently set SKU Id. + +Prototype: + +typedef +UINTN +(EFIAPI *EFI_GET_PCD_INFO_PPI_GET_SKU) ( + VOID + ); + + +Description: +GetSku() returns the currently set SKU Id. If the platform has not set at a SKU Id, then the default SKU Id value of 0 is returned. If the platform has set a SKU Id, then the currently set SKU Id is returned. +``` + +Sometimes it is necessary to get correct PCDs for the target SKU as much early as possible. This is why often the firmware sets the SkuId exactly in the PEI phase using the functions above. + +# `LibPcdSetSku`/`LibPcdGetSku` + +The PCD library has couple of functions that simplify SKU get/set operations [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 +/** + This function provides a means by which SKU support can be established in the PCD infrastructure. + Sets the current SKU in the PCD database to the value specified by SkuId. SkuId is returned. + @param SkuId The SKU value that will be used when the PCD service retrieves and sets values + associated with a PCD token. + @return Return the SKU ID that was set. +**/ +UINTN +EFIAPI +LibPcdSetSku ( + IN UINTN SkuId + ); +``` +```cpp +/** + Retrieve the currently set SKU Id. + @return The currently set SKU Id. If the platform has not set at a SKU Id, then the + default SKU Id value of 0 is returned. If the platform has set a SKU Id, then the currently set SKU + Id is returned. +**/ +UINTN +EFIAPI +LibPcdGetSku ( + VOID + ); +``` + +The functions have different realization in PEI [https://github.com/tianocore/edk2/blob/master/MdePkg/Library/PeiPcdLib/PeiPcdLib.c](https://github.com/tianocore/edk2/blob/master/MdePkg/Library/PeiPcdLib/PeiPcdLib.c) and in DXE [https://github.com/tianocore/edk2/blob/master/MdePkg/Library/DxePcdLib/DxePcdLib.c](https://github.com/tianocore/edk2/blob/master/MdePkg/Library/DxePcdLib/DxePcdLib.c), since the PCD datbase protocol is different in these phases. But basically this library functions call the same PCD protocol functions that we did in our example program. + +Using these functions we can simplify our application: +```cpp +#include +#include +#include + +VOID Usage() +{ + Print(L"Program to set Sku number\n\n"); + Print(L"Usage: SetSku \n"); +} + +INTN +EFIAPI +ShellAppMain ( + IN UINTN Argc, + IN CHAR16 **Argv + ) +{ + if (Argc != 2) { + Usage(); + return EFI_INVALID_PARAMETER; + } + + UINTN SkuNumber; + RETURN_STATUS ReturnStatus; + ReturnStatus = StrHexToUintnS(Argv[1], NULL, &SkuNumber); + if (RETURN_ERROR(ReturnStatus)) { + Print(L"Error! Can't convert SkuId string to number\n"); + return EFI_INVALID_PARAMETER; + } + + LibPcdSetSku(SkuNumber); + UINTN SkuId = LibPcdGetSku(); + if (SkuId != SkuNumber) { + Print(L"Failed to change SkuId to %ld, SkuId is still %ld\n", SkuNumber, SkuId); + return EFI_UNSUPPORTED; + } + + Print(L"Sku is changed successfully to %ld\n", SkuNumber); + + return EFI_SUCCESS; +} +``` # Links diff --git a/Lessons_uncategorized/Lesson_SKU/UefiLessonsPkg/SetSku/SetSku.c b/Lessons_uncategorized/Lesson_SKU/UefiLessonsPkg/SetSku/SetSku.c new file mode 100644 index 0000000..8683947 --- /dev/null +++ b/Lessons_uncategorized/Lesson_SKU/UefiLessonsPkg/SetSku/SetSku.c @@ -0,0 +1,72 @@ +#include +#include + +#include +#include +#include + +//#include + +VOID Usage() +{ + Print(L"Program to set Sku number\n\n"); + Print(L" Usage: SetSku \n"); +} + +INTN +EFIAPI +ShellAppMain ( + IN UINTN Argc, + IN CHAR16 **Argv + ) +{ + EFI_STATUS Status; + + if (Argc != 2) { + Usage(); + return EFI_INVALID_PARAMETER; + } + + UINTN SkuNumber; + RETURN_STATUS ReturnStatus; + ReturnStatus = StrHexToUintnS(Argv[1], NULL, &SkuNumber); + if (RETURN_ERROR(ReturnStatus)) { + Print(L"Error! Can't convert SkuId string to number\n"); + return EFI_INVALID_PARAMETER; + } + + EFI_PCD_PROTOCOL* pcdProtocol; + Status = gBS->LocateProtocol(&gEfiPcdProtocolGuid, + NULL, + (VOID **)&pcdProtocol); + if (EFI_ERROR(Status)) { + Print(L"Error! Can't find EFI_PCD_PROTOCOL\n"); + return EFI_UNSUPPORTED; + } + + EFI_GET_PCD_INFO_PROTOCOL* getPcdInfoProtocol; + Status = gBS->LocateProtocol(&gEfiGetPcdInfoProtocolGuid, + NULL, + (VOID **)&getPcdInfoProtocol); + if (EFI_ERROR(Status)) { + Print(L"Error! Can't find EFI_GET_PCD_INFO_PROTOCOL\n"); + return EFI_UNSUPPORTED; + } + + + pcdProtocol->SetSku(SkuNumber); + + UINTN SkuId = getPcdInfoProtocol->GetSku(); +/* + LibPcdSetSku(SkuNumber); + UINTN SkuId = LibPcdGetSku(); +*/ + if (SkuId != SkuNumber) { + Print(L"Failed to change SkuId to 0x%lx, SkuId is still 0x%lx\n", SkuNumber, SkuId); + return EFI_UNSUPPORTED; + } + + Print(L"Sku is changed successfully to 0x%lx\n", SkuNumber); + + return EFI_SUCCESS; +} diff --git a/Lessons_uncategorized/Lesson_SKU/UefiLessonsPkg/SetSku/SetSku.inf b/Lessons_uncategorized/Lesson_SKU/UefiLessonsPkg/SetSku/SetSku.inf new file mode 100644 index 0000000..b14aed8 --- /dev/null +++ b/Lessons_uncategorized/Lesson_SKU/UefiLessonsPkg/SetSku/SetSku.inf @@ -0,0 +1,17 @@ +[Defines] + INF_VERSION = 1.25 + BASE_NAME = SetSku + FILE_GUID = 8cb96f21-d1dc-4f4a-8064-c3a5b556f73a + MODULE_TYPE = UEFI_APPLICATION + VERSION_STRING = 1.0 + ENTRY_POINT = ShellCEntryLib + +[Sources] + SetSku.c + +[Packages] + MdePkg/MdePkg.dec + +[LibraryClasses] + ShellCEntryLib + UefiLib -- cgit v1.2.3-18-g5258