55. PlatformLangCodes EFI 변수 수정 및 다른 언어를 동적 추가하기

우리는 새 언어에 대한 글꼴을 추가해주는 방법을 학습했으며, 시스템의 일부 기존 언어에 대한 문자열을 동적으로 채우는 방법을 알게 되었다. 이번 장에서는 다른 언어를 동적으로 생성할 수 있는지 살펴보겠다.

Select Language 메뉴는 PlatformLangCodes EFI 변수 값에서 가능한 모든 언어 옵션을 가져온다. 그리고 현재 언어 옵션은 PlatformLang EFI 옵션에 반영된다.

이러한 옵션의 값은 https://github.com/tianocore/edk2/blob/master/MdePkg/MdePkg.dec서 PCD의 도움으로 설정된다.

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

https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Application/UiApp/FrontPageCustomizedUiSupport.c 에서 UiCreateLanguageMenu 함수의 실제 코드를 볼 수 있다.

PlatformLangCodes를 수정하고, 다른 언어를 추가할 수 있는지 살펴보자.

먼저 PlatformLangCodes 옵션의 값을 출력하는 애플리케이션을 만든다.

#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;
}

이전에 이미 gRT->GetVariable 프로토콜 함수를 직접 사용한 적이 있으므로, 여기서는 GetEfiGlobalVariable2 라이브러리 함수를 사용하여 코드를 단순화한다. 이 함수는 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
  )

애플리케이션을 빌드하고 실행하면, PCD에서 값을 얻을 수 있다.

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

이제 변수 끝에 ;ru-RU를 추가하고 다시 작성해보자.

먼저 새 문자열을 구성하고 필요한 데이터로 채운다.

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

ASCII 문자열 함수가 https://github.com/tianocore/edk2/blob/master/MdePkg/Library/BaseLib/String.c 정의된 경우에 대비. 또한 AllocatePool 함수에 대한 Library/MemoryAllocationLib.h 헤더와 CopyMem 함수에 대한 Library/BaseMemoryLib.h 헤더를 포함하는 것을 잊지 않도록 하자.

이제 SetVariable 호출을 사용하여 변수를 업데이트한다.

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은 Runtime 서비스이며, UEFI 스펙에서 해당 정의를 찾을 수 있다.

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.

gRTUefiRuntimeServicesTableLib 라이브러리의 SystemTable->RuntimeServices에 대한 바로가기이다. 따라서 라이브러리 헤더 <Library/UefiRuntimeServicesTableLib.h>를 포함하는 것을 잊지 않도록 하자.

애플리케이션을 빌드하고 실행하면, 다음과 같은 결과를 얻을 수 있다.

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

안타깝게도, 'PlatformLangCodes' EFI 변수가 쓰기 금지되어 있으므로 Runtime 시, 새 언어를 추가할 수 없다. 따라서 Runtime에 다른 로컬화 언어를 추가할 수 없다.

UEFI 스펙을 보면 다음과 같이 표시된다.

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

PlatformLangCodesgEdkiiVariablePolicyProtocolGuid 프로토콜의 도움으로 수정할 수 있도록 잠겨 있다. 이는 변수에 대해 다른 정책을 설정하기 위한 사용자 정의 EDKII 프로토콜이다.

https://github.com/tianocore/edk2/tree/master/MdeModulePkg/Library/VariablePolicyLib/ReadMe.md 보면, UEFI 변수 정책 프로토콜에 대한 자세한 내용을 알 수 있다.

https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Include/Protocol/VariablePolicy.h 아래에 헤더파일이 존재한다.

PlatformLangCodes EFI 변수에 대한 정책은 몇가지 다른 변수와 함께 https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/BdsDxe/BdsEntry.c 설정된다.

///
/// 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()

DisableVariablePolicy()를 수행하여, VariablePolicyProtocol을 비활성화할 수 있다.

$ vi 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;
  }
}
$ vi UefiLessonsPkg/AddNewLanguage/AddNewLanguage.inf
---
....
[Packages]
  ...
  MdeModulePkg/MdeModulePkg.dec
...

[Protocols]
  gEdkiiVariablePolicyProtocolGuid

하지만, 이 호출에 에러가 발생한다.

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

이는 DXE UEFI 단계의 변수 정책이 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(잠금)은 더 이상 변수에 대한 정책을 변경하거나, 비활성화할 수 없음을 의미한다. 따라서 Runtime에 PlatformLangCodes를 변경할 방법이 없다.

Last updated