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
|
In this lesson we would investigate how to modify form elements that have keyword references.
But rather than change some system variables let's add keyword references to our own application.
# Adding keywords to form elements
For an example let's take our `HIIFormDataElements` driver. Adding keywords is very simple. The only file that we would need to modify is UNI file (`Strings.uni` in this case).
First we need to add a new namespace id. It is added via the new language definition with an identificator in a format `x-UEFI-*`. Let's use `x-UEFI-OEM` in our case. The language text representation is not important, here we use `"OEM_NameSpace"` as a placeholder:
```
#langdef x-UEFI-OEM "OEM_NameSpace"
```
Now we need to add `#language x-UEFI-OEM "<...>"` string translations to the prompts of all form elements that we want to refer by keywords. For example let's add keys to the Checkbox, Numeric and String elements:
```
...
#string CHECKBOX_PROMPT #language en-US "Checkbox prompt"
#language x-UEFI-OEM "CheckboxKey"
#string NUMERIC_PROMPT #language en-US "Numeric prompt"
#language x-UEFI-OEM "NumericKey"
#string STRING_PROMPT #language en-US "String prompt"
#language x-UEFI-OEM "StringKey"
...
```
The provided values inside these translation strings would act as keywords for these elements. Just in case, it is completely allowed to use space `" "` inside a key name.
Now build the updated driver and load it in the UEFI shell. Here I've decided to create a new driver `HIIFormDataElementsWithKeywords` to keep some separation between the lessons, but generally it is just a copy of `HIIFormDataElements.efi` with the modifications described above:
```
FS0:\> load HIIFormDataElementsWithKeywords.efi
Image 'FS0:\HIIFormDataElementsWithKeywords.efi' loaded at 6880000 - Success
```
Now use `HIIKeyword` application to dump all the keys of our new namespace:
```
FS0:\> HIIKeyword.efi get "NAMESPACE=x-UEFI-OEM" ""
Response: NAMESPACE=x-UEFI-OEM&PATH=0104140075f599c2ddf17d4db7aae5064b3ecbd77fff0400&KEYWORD=CheckboxKey&VALUE=00&NAMESPACE=x-UEFI-OEM&PATH=0104140075f599c2ddf17d4db7aae5064b3ecbd77fff0400&KEYWORD=NumericKey&VALUE=0000&NAMESPACE=x-UEFI-OEM&PATH=0104140075f599c2ddf17d4db7aae5064b3ecbd77fff0400&KEYWORD=StringKey&VALUE=000000<...>
NAMESPACE=x-UEFI-OEM
PATH=0104140075f599c2ddf17d4db7aae5064b3ecbd77fff0400 (VenHw(C299F575-F1DD-4D7D-B7AA-E5064B3ECBD7))
KEYWORD=CheckboxKey
VALUE=00
00 | .
NAMESPACE=x-UEFI-OEM
PATH=0104140075f599c2ddf17d4db7aae5064b3ecbd77fff0400 (VenHw(C299F575-F1DD-4D7D-B7AA-E5064B3ECBD7))
KEYWORD=NumericKey
VALUE=0000
00 00 | ..
NAMESPACE=x-UEFI-OEM
PATH=0104140075f599c2ddf17d4db7aae5064b3ecbd77fff0400 (VenHw(C299F575-F1DD-4D7D-B7AA-E5064B3ECBD7))
KEYWORD=StringKey
VALUE=0000000000000000000000000000000000000000
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
00 00 00 00 | ....
```
You can verify that it is possible to access individual keys:
```
FS0:\> HIIKeyword.efi get "NAMESPACE=x-UEFI-OEM" "KEYWORD=NumericKey"
Response: NAMESPACE=x-UEFI-OEM&PATH=0104140075f599c2ddf17d4db7aae5064b3ecbd77fff0400&KEYWORD=NumericKey&VALUE=0000
NAMESPACE=x-UEFI-OEM
PATH=0104140075f599c2ddf17d4db7aae5064b3ecbd77fff0400 (VenHw(C299F575-F1DD-4D7D-B7AA-E5064B3ECBD7))
KEYWORD=NumericKey
VALUE=0000
00 00 | ..
```
# Use `EFI_KEYWORD_HANDLER_PROTOCOL.SetData()` to modify keyword data
Now we are ready to add support for keyword data modification to the `HIIKeyword` application. For this we will need `SetData()` function from the `EFI_KEYWORD_HANDLER_PROTOCOL`:
```cpp
EFI_KEYWORD_HANDLER_PROTOCOL.SetData()
Summary:
Set the data associated with a particular configuration namespace keyword.
Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_KEYWORD_HANDLER _SET_DATA) (
IN EFI_KEYWORD_HANDLER_PROTOCOL *This,
IN CONST EFI_STRING KeywordString,
OUT EFI_STRING *Progress,
OUT UINT32 *ProgressErr
);
Parameters:
This Pointer to the EFI_KEYWORD_HANDLER _PROTOCOL instance.
KeywordString A null-terminated string in <MultiKeywordResp> format.
Progress On return, points to a character in the KeywordString. Points to the string’s NULL terminator if the request was successful.
Points to the most recent ‘&’ before the first failing name / value pair (or the beginning of the string if the failure is in the first name / value pair) if the request was not successful.
ProgressErr If during the processing of the KeywordString there was a failure, this parameter gives additional information about the possible source of the problem.
Description:
This function accepts a <MultiKeywordResp> formatted string, finds the associated keyword owners, creates a <MultiConfigResp> string from it and forwards it to the EFI_HII_ROUTING_PROTOCOL.RouteConfig function.
```
The call signature of our program would be modified like this:
```cpp
VOID Usage()
{
Print(L"Usage:\n");
Print(L"HIIKeyword get <NamespaceStr> <KeywordStr>\n");
Print(L"HIIKeyword set <KeywordStr>\n");
}
```
The support code is even simplier than in the `HIIKeyword get <...>` case:
```cpp
EFI_STRING Progress;
UINT32 ProgressErr;
if (!StrCmp(Argv[1], L"get")) {
<...>
} else if (!StrCmp(Argv[1], L"set")) {
if (Argc != 3) {
Print(L"Wrong argument!\n");
Usage();
return EFI_INVALID_PARAMETER;
}
Status = gHiiConfigKeywordHandler->SetData(gHiiConfigKeywordHandler,
Argv[2],
&Progress,
&ProgressErr);
if (StrCmp(Progress, L'\0')) {
Print(L"Part of string was unparsed %s\n", Progress);
}
if (ProgressErr) {
Print(L"Error! ProgressErr=%s\n", ProgressErrorStr(ProgressErr));
}
if (EFI_ERROR(Status)) {
Print(L"Error! SetData returned %r\n", Status);
return Status;
}
} else {
Print(L"Wrong argument!\n");
Usage();
return EFI_INVALID_PARAMETER;
}
```
So let's rebuild `HIIKeyword.efi` and test this new functionality. Once again first load our custom form driver:
```
FS0:\> load HIIFormDataElementsWithKeywords.efi
Image 'FS0:\HIIFormDataElementsWithKeywords.efi' loaded at 6880000 - Success
```
Now with the `HIIKeyword.efi` try to change numeric element value to 7:
```
FS0:\> HIIKeyword.efi set "NAMESPACE=x-UEFI-OEM&KEYWORD=NumericKey&VALUE=0007"
```
Verify that the modification was completed successfully:
```
FS0:\> HIIKeyword.efi get "NAMESPACE=x-UEFI-OEM" "KEYWORD=NumericKey"
Response: NAMESPACE=x-UEFI-OEM&PATH=0104140075f599c2ddf17d4db7aae5064b3ecbd77fff0400&KEYWORD=NumericKey&VALUE=0007
NAMESPACE=x-UEFI-OEM
PATH=0104140075f599c2ddf17d4db7aae5064b3ecbd77fff0400 (VenHw(C299F575-F1DD-4D7D-B7AA-E5064B3ECBD7))
KEYWORD=NumericKey
VALUE=0007
07 00 | ..
```
If you want to, you can even check the form browser:
![1](1.png?raw=true "1")
I want to point out that like in the `RouteConfig()` case, modification through the `EFI_KEYWORD_HANDLER_PROTOCOL.SetData()` call bypasses some of the VFR checks. For example even if the current numeric value limits are `0..10`
```
numeric
...
minimum = 0,
maximum = 10,
...
endnumeric;
```
it is still possible to change it to something like `0xABCD` like this:
```
FS0:\> HIIKeyword.efi set "NAMESPACE=x-UEFI-OEM&KEYWORD=NumericKey&VALUE=ABCD"
```
Once again you can verify updated value with our program:
```
FS0:\> HIIKeyword.efi get "NAMESPACE=x-UEFI-OEM" "KEYWORD=NumericKey"
Response: NAMESPACE=x-UEFI-OEM&PATH=0104140075f599c2ddf17d4db7aae5064b3ecbd77fff0400&KEYWORD=NumericKey&VALUE=abcd
NAMESPACE=x-UEFI-OEM
PATH=0104140075f599c2ddf17d4db7aae5064b3ecbd77fff0400 (VenHw(C299F575-F1DD-4D7D-B7AA-E5064B3ECBD7))
KEYWORD=NumericKey
VALUE=abcd
CD AB | ..
```
Or with the form browser:
![2](2.png?raw=true "2")
|