Skip to content

Latest commit

 

History

History

Lesson_55

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

We know how to add font for the new language, and we now how to populate strings for some of the existing languages in the system dynamically. Let's see if it is possible to create another language option dynamically.

Select Language menu gets all possible language options from the value of the PlatformLangCodes EFI variable. And the current language option is reflected by the PlatformLang EFI option.

Initially the values for these options are set with a help of PCD in the https://github.com/tianocore/edk2/blob/master/MdePkg/MdePkg.dec:

  ## Default platform supported RFC 4646 languages: (American) English & French.
  # @Prompt Default Value of PlatformLangCodes Variable.
  gEfiMdePkgTokenSpaceGuid.PcdUefiVariableDefaultPlatformLangCodes|"en;fr;en-US;fr-FR"|VOID*|0x0000001e

  ## Default current RFC 4646 language: (American) English.
  # @Prompt Default Value of PlatformLang Variable.
  gEfiMdePkgTokenSpaceGuid.PcdUefiVariableDefaultPlatformLang|"en-US"|VOID*|0x0000001f

You can look at the actual code in the UiCreateLanguageMenu function from the https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Application/UiApp/FrontPageCustomizedUiSupport.c.

Let's see if it is possible to modify PlatformLangCodes and add another language into it.

First create an app that would print the value for the PlatformLangCodes option:

#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS Status;

  CHAR8* LanguageString;
  Status = GetEfiGlobalVariable2(L"PlatformLangCodes", (VOID**)&LanguageString, NULL);
  if (EFI_ERROR(Status)) {
    Print(L"Error! Can't perform GetEfiGlobalVariable2, status=%r\n", Status);
    return Status;
  }
  Print(L"Current value of the 'PlatformLangCodes' variable is '%a'\n", LanguageString);

  return EFI_SUCCESS;
}

We've already used gRT->GetVariable protocol function directly before, so here we use GetEfiGlobalVariable2 library function to the simplify code. This function is defined in the https://github.com/tianocore/edk2/blob/master/MdePkg/Library/UefiLib/UefiLib.c:

**
  Returns a pointer to an allocated buffer that contains the contents of a
  variable retrieved through the UEFI Runtime Service GetVariable().  This
  function always uses the EFI_GLOBAL_VARIABLE GUID to retrieve variables.
  The returned buffer is allocated using AllocatePool().  The caller is
  responsible for freeing this buffer with FreePool().
  If Name is NULL, then ASSERT().
  If Value is NULL, then ASSERT().
  @param[in]  Name  The pointer to a Null-terminated Unicode string.
  @param[out] Value The buffer point saved the variable info.
  @param[out] Size  The buffer size of the variable.
  @return EFI_OUT_OF_RESOURCES      Allocate buffer failed.
  @return EFI_SUCCESS               Find the specified variable.
  @return Others Errors             Return errors from call to gRT->GetVariable.
**/
EFI_STATUS
EFIAPI
GetEfiGlobalVariable2 (
  IN CONST CHAR16    *Name,
  OUT VOID           **Value,
  OUT UINTN          *Size OPTIONAL
  )

If you build and run our application now, you would get the value from the PCD:

FS0:\> AddNewLanguage.efi
Current value of the 'PlatformLangCodes' variable is 'en;fr;en-US;fr-FR'

Now let's try to add ;ru-RU to the end of the variable and write it back.

First construct new string and fill it with the necessary data:

CHAR8* NewLanguageString = AllocatePool(AsciiStrLen(LanguageString) + AsciiStrSize(";ru-RU"));
if (NewLanguageString == NULL) {
  Print(L"Error! Can't allocate size for new PlatformLangCodes variable\n");
  FreePool(LanguageString);
  return EFI_OUT_OF_RESOURCES;
}

CopyMem(NewLanguageString, LanguageString, AsciiStrLen(LanguageString));
CopyMem(&NewLanguageString[AsciiStrLen(LanguageString)], ";ru-RU", AsciiStrSize(";ru-RU"));

Print(L"Set 'PlatformLangCodes' variable to '%a'\n", NewLanguageString);

Just in case ASCII string functions are defined in the https://github.com/tianocore/edk2/blob/master/MdePkg/Library/BaseLib/String.c. Also don't forget to include Library/MemoryAllocationLib.h header for the AllocatePool function and Library/BaseMemoryLib.h header for the CopyMem function.

Now use SetVariable call to update variable:

Status = gRT->SetVariable (
              L"PlatformLangCodes",
              &gEfiGlobalVariableGuid,
              EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
              AsciiStrSize(NewLanguageString),
              NewLanguageString
              );
if (EFI_ERROR(Status)) {
  Print(L"Error! Can't set 'PlatformLangCodes' variable, status=%r\n", Status);
}

SetVariable is a Runtime Service, you can find its definition in the UEFI specification:

SetVariable()

Summary:
Sets the value of a variable.

Prototype:
typedef
EFI_STATUS
SetVariable (
 IN CHAR16 *VariableName,
 IN EFI_GUID *VendorGuid,
 IN UINT32 Attributes,
 IN UINTN DataSize,
 IN VOID *Data
 );

Parameters:
VariableName 	A Null-terminated string that is the name of the vendor’s variable. Each VariableName is unique for each VendorGuid.
VendorGuid 	A unique identifier for the vendor.
Attributes 	Attributes bitmask to set for the variable.
DataSize 	The size in bytes of the Data buffer.
Data 		The contents for the variable.

In case you forgot gRT is a shortcut for the SystemTable->RuntimeServices from the UefiRuntimeServicesTableLib library. So don't forget to include its library header <Library/UefiRuntimeServicesTableLib.h>.

If you build and run our application now you would get:

FS0:\> AddNewLanguage.efi
Current value of the 'PlatformLangCodes' variable is 'en;fr;en-US;fr-FR'
Set 'PlatformLangCodes' variable to 'en;fr;en-US;fr-FR;ru-RU'
Error! Can't set 'PlatformLangCodes' variable, status=Write Protected

Unfortunately it is not possible to add new language at runtime as 'PlatformLangCodes' EFI variable is write protected. Therefore it is not possible to add another localization language at runtime.

If you look at the UEFI spec you'll see:

The PlatformLangCodes variable contains a null- terminated ASCII string representing the language
codes that the firmware can support. At initialization time the firmware computes the supported
languages and creates this data variable. Since the firmware creates this value on each initialization, its
contents are not stored in nonvolatile memory. This value is considered read-only. 

EDKII_VARIABLE_POLICY_PROTOCOL

The PlatformLangCodes is locked for modifications with a help of a gEdkiiVariablePolicyProtocolGuid protocol. This is a custom EDKII protocol for setting different policies on variables.

You can read more about the UEFI Variable Policy protocol in the https://github.com/tianocore/edk2/tree/master/MdeModulePkg/Library/VariablePolicyLib/ReadMe.md

The header file is placed under https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Include/Protocol/VariablePolicy.h

The policy for the PlatformLangCodes EFI variable is set in the https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/BdsDxe/BdsEntry.c along with couple of other variables:

///
/// The read-only variables defined in UEFI Spec.
///
CHAR16  *mReadOnlyVariables[] = {
  EFI_PLATFORM_LANG_CODES_VARIABLE_NAME,             // L"PlatformLangCodes"  The language codes that the firmware supports
  EFI_LANG_CODES_VARIABLE_NAME,
  EFI_BOOT_OPTION_SUPPORT_VARIABLE_NAME,
  EFI_HW_ERR_REC_SUPPORT_VARIABLE_NAME,
  EFI_OS_INDICATIONS_SUPPORT_VARIABLE_NAME
  };

...

  // Mark the read-only variables if the Variable Lock protocol exists
  //
  Status = gBS->LocateProtocol(&gEdkiiVariablePolicyProtocolGuid,
NULL, (VOID**)&VariablePolicy);
  DEBUG((DEBUG_INFO, "[BdsDxe] Locate Variable Policy protocol -
%r\n", Status));
  if (!EFI_ERROR (Status)) {
    for (Index = 0; Index < ARRAY_SIZE (mReadOnlyVariables); Index++) {
      Status = RegisterBasicVariablePolicy(
                 VariablePolicy,
                 &gEfiGlobalVariableGuid,
                 mReadOnlyVariables[Index],
                 VARIABLE_POLICY_NO_MIN_SIZE,
                 VARIABLE_POLICY_NO_MAX_SIZE,
                 VARIABLE_POLICY_NO_MUST_ATTR,
                 VARIABLE_POLICY_NO_CANT_ATTR,
                 VARIABLE_POLICY_TYPE_LOCK_NOW
                 );
      ASSERT_EFI_ERROR(Status);
    }
  }

Try to execute DisableVariablePolicy()

We can try to perform DisableVariablePolicy() to disable VariablePolicyProtocol.

UefiLessonsPkg/AddNewLanguage/AddNewLanguage.c

...
#include <Protocol/VariablePolicy.h>

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  ...

  EDKII_VARIABLE_POLICY_PROTOCOL* VariablePolicyProtocol;
  Status = gBS->LocateProtocol(&gEdkiiVariablePolicyProtocolGuid,
                               NULL, 
                               (VOID**)&VariablePolicyProtocol);
  if (EFI_ERROR(Status)) {
    Print(L"Error! Could not find Variable Policy protocol: %r\n", Status);
    return Status;
  }  
  Status = VariablePolicyProtocol->DisableVariablePolicy();
  if (EFI_ERROR(Status)) {
    Print(L"Error! Can't disable VariablePolicy: %r\n", Status);
    return Status;
  }
}

UefiLessonsPkg/AddNewLanguage/AddNewLanguage.inf:

....
[Packages]
  ...
  MdeModulePkg/MdeModulePkg.dec
...

[Protocols]
  gEdkiiVariablePolicyProtocolGuid

But unfortunately this call would fail:

FS0:\> AddNewLanguage.efi
Current value of the 'PlatformLangCodes' variable is 'en;fr;en-US;fr-FR'
Set 'PlatformLangCodes' variable to 'en;fr;en-US;fr-FR;ru-RU'
Error! Can't set PlatformLangCodes variable, status=Write Protected
Error! Can't disable VariablePolicy: Write Protected

This happend because in the end of the DXE UEFI stage variable policy is locked in the MdeModulePkg/Universal/Variable/RuntimeDxe/VariableDxe.c:

VOID
EFIAPI
OnEndOfDxe (
  EFI_EVENT Event,
  VOID *Context
)
{
  EFI_STATUS Status;
  DEBUG ((EFI_D_INFO, "[Variable]END_OF_DXE is signaled\n"));
   ...
  Status = LockVariablePolicy ();
   ...
}

Locking means that is no longer possible to change or disable policy for variables. Therefore there is no way to change PlatformLangCodes at runtime.