aboutsummaryrefslogtreecommitdiffstats
path: root/Lessons/Lesson_55/README.md
blob: 123f439887198c60c0acfcf5247c3b5fcc5772ee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
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.