aboutsummaryrefslogtreecommitdiffstats
path: root/Lessons/Lesson_57/README.md
blob: ecbf8b7233685331fb2f022c00f7ae8f61780c36 (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
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
The main purpose of HII is to present to the user configuration menus to control UEFI settings.

We've already covered HII strings and HII fonts. Now it is time to talk about HII forms - the final element, that glues everything together.

The data in HII form packages is encoded in a special IFR format where IFR stands for the Internal Form Representation. It is not easy to construct forms packages by hand, as IFR is not very human readable. It is a series of operational codes (opcodes), and its parsing process can be very tedious.

To ease things EDKII offers a way to write HII forms in a special human friendly language called VFR. Here VFR stands for Visual Form Representation (opposed to Internal Form Representation) and its specification can be found under the https://edk2-docs.gitbook.io/edk-ii-vfr-specification/
EDKII has a special utility called `VfrCompile` to transorm VFR code to C arrays with IFR opcodes https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/VfrCompile

Let's try to create an application that would show us a simple form.

Create new `HIISimpleForm` application with a HiiLib.
```
[Defines]
  INF_VERSION                    = 1.25
  BASE_NAME                      = HIISimpleForm
  FILE_GUID                      = df2f1465-2bf1-492c-af6c-232ac40bdf82
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain

[Sources]
  HIISimpleForm.c

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

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib
  HiiLib
```
```
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>

#include <Library/HiiLib.h>

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  return EFI_SUCCESS;
}
```
Add it to the DSC file UefiLessonsPkg/UefiLessonsPkg.dsc:
```
[Components]
  ...
  UefiLessonsPkg/HIISimpleForm/HIISimpleForm.inf
```

And this is our first VFR `UefiLessonsPkg/HIISimpleForm/Form.vfr`:
```
#define HIISIMPLEFORM_FORMSET_GUID  {0xef2acc91, 0x7b50, 0x4ab9, {0xab, 0x67, 0x2b, 0x4, 0xf8, 0xbc, 0x13, 0x5e}}

formset
  guid     = HIISIMPLEFORM_FORMSET_GUID,
  title    = STRING_TOKEN(HIISIMPLEFORM_FORMSET_TITLE),
  help     = STRING_TOKEN(HIISIMPLEFORM_FORMSET_HELP),
endformset;
```

Everything in VFR must be encoded inside the `formset` component. This component starts with the `formset` keyword and ends with the `endformset` keyword. The `formset` component must have the 3 mandatory fields: `guid`, `title` and `help`.

We can encode `guid` either in place:
```
guid     = {0xef2acc91, 0x7b50, 0x4ab9, {0xab, 0x67, 0x2b, 0x4, 0xf8, 0xbc, 0x13, 0x5e}}
```
Or with a help of a define statement which is similar to C syntax like we did above.

The `title` and `help` fields should contain string IDs of the strings. Therefore to get them we use `STRING_TOKEN(...)`, and the strings itself should be encoded in the UNI file (in our case `UefiLessonsPkg/HIISimpleForm/Strings.uni`):
```
#langdef en-US "English"

#string HIISIMPLEFORM_FORMSET_TITLE          #language en-US  "Simple Formset"
#string HIISIMPLEFORM_FORMSET_HELP           #language en-US  "This is a very simple formset"
```

The `title` and `help` fields would be visible if our formset would be included into another formset. For example look at the https://github.com/tianocore/edk2/blob/master/OvmfPkg/PlatformDxe/PlatformForms.vfr:
```
formset
  guid  = OVMF_PLATFORM_CONFIG_GUID,
  title = STRING_TOKEN(STR_FORMSET_TITLE),
  help  = STRING_TOKEN(STR_FORMSET_HELP),
  ...
endformset;
```
And https://github.com/tianocore/edk2/blob/master/OvmfPkg/PlatformDxe/Platform.uni
```
#langdef en-US "English"

#string STR_FORMSET_TITLE        #language en-US "OVMF Platform Configuration"
#string STR_FORMSET_HELP         #language en-US "Change various OVMF platform settings."
```
This would produce this output in the BIOS menu:
![Formset_title_help](Formset_title_help.png?raw=true "Formset_title_help")


Okay, now that we have VFR and UNI files it is time to publish our form to the HII. Add VFR and UNI files to the `Sources` section in the `UefiLessonsPkg/HIISimpleForm/HIISimpleForm.inf`:
```
[Sources]
  ...
  Strings.uni
  Form.vfr
```

Here is a code that would populate our form and its strings to the HII database `UefiLessonsPkg/HIISimpleForm/HIISimpleForm.c`:
```
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>

#include <Library/HiiLib.h>

extern UINT8 FormBin[];

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_HII_HANDLE Handle = HiiAddPackages(
                             &gEfiCallerIdGuid,
                             NULL,
                             HIISimpleFormStrings,
                             FormBin,
                             NULL
                             );
  if (Handle == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  return EFI_SUCCESS;
}
```

As you can see here we use `FormBin` array to publish our form data. EDKII build system generates HII form package from every VFR file, and puts its data into the `<VFR name>Bin` array. Like with the UNI files this array is prependend with a 4-byte packages size header. Therefore it can be use with `HiiAddPackages` library function as-is.
Also as this array would be declared in the autogenerated *.c file (and not in *.h file), we also need to declare it as an `extern` in our file.

But let's see it ourself. Build module and look at the `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/HIISimpleForm/HIISimpleForm/DEBUG/Form.c`
```
unsigned char FormBin[] = {
  // ARRAY LENGTH

  0x3D,  0x00,  0x00,  0x00,

  // PACKAGE HEADER

  0x39,  0x00,  0x00,  0x02,

  // PACKAGE DATA

  0x0E,  0xA7,  0x91,  0xCC,  0x2A,  0xEF,  0x50,  0x7B,  0xB9,  0x4A,  0xAB,  0x67,  0x2B,  0x04,  0xF8,  0xBC,
  0x13,  0x5E,  0x02,  0x00,  0x03,  0x00,  0x01,  0x71,  0x99,  0x03,  0x93,  0x45,  0x85,  0x04,  0x4B,  0xB4,
  0x5E,  0x32,  0xEB,  0x83,  0x26,  0x04,  0x0E,  0x5C,  0x06,  0x00,  0x00,  0x00,  0x00,  0x5C,  0x06,  0x00,
  0x00,  0x01,  0x00,  0x29,  0x02

};
```

Build system also produces one more interesting file. Look at the content of the `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/HIISimpleForm/HIISimpleForm/DEBUG/Form.lst`:
```
//
//  VFR compiler version  2.01 (UEFI 2.4) Developer Build based on Revision: Unknown
//
extern unsigned char HIISimpleFormStrings[];
formset
>00000000: 0E A7 91 CC 2A EF 50 7B B9 4A AB 67 2B 04 F8 BC 13 5E 02 00 03 00 01 71 99 03 93 45 85 04 4B B4 5E 32 EB 83 26 04 0E
>00000027: 5C 06 00 00 00 00
>0000002D: 5C 06 00 00 01 00
  guid = {0xef2acc91, 0x7b50, 0x4ab9, {0xab, 0x67, 0x2b, 0x4, 0xf8, 0xbc, 0x13, 0x5e}},
  title = STRING_TOKEN(0x0002),
  help = STRING_TOKEN(0x0003),
endformset;
>00000033: 29 02

//
// All Opcode Record List
//
>00000000: 0E A7 91 CC 2A EF 50 7B B9 4A AB 67 2B 04 F8 BC 13 5E 02 00 03 00 01 71 99 03 93 45 85 04 4B B4 5E 32 EB 83 26 04 0E
>00000027: 5C 06 00 00 00 00
>0000002D: 5C 06 00 00 01 00
>00000033: 29 02

Total Size of all record is 0x00000035
```
Here you can see how the `PACKAGE DATA` part in the `FormBin` array is constructed from the IFR opcodes with some comments about relations to the responsible VFR code.

# IFR data parsing

Our data currently consist of 4 components: `EFI_IFR_FORM_SET`, two `EFI_IFR_DEFAULTSTORE` and `EFI_IFR_END`. Each of these take one string of data in the `Form.lst`.

Like with String packages every possible component has a common header. So let's look at its definition first:
```
EFI_IFR_OP_HEADER:

Summary:
Standard opcode header

Prototype:
typedef struct _EFI_IFR_OP_HEADER {
 UINT8 OpCode;
 UINT8 Length:7;
 UINT8 Scope:1;
} EFI_IFR_OP_HEADER;

Members:
OpCode 		Defines which type of operation is being described by this header.
Length 		Defines the number of bytes in the opcode, including this header.
Scope   	If this bit is set, the opcode begins a new scope, which is ended by an EFI_IFR_END opcode.

Description:
Forms are represented in a binary format roughly similar to processor instructions. Each header contains an opcode, a length and a scope indicator.
If Scope indicator is set, the scope exists until it reaches a corresponding EFI_IFR_END opcode. Scopes may be nested within other scopes.
```

Now here is a definition of a `EFI_IFR_FORM_SET`:
```
EFI_IFR_FORM_SET

Summary:
The form set is a collection of forms that are intended to describe the pages that will be displayed to the user.

Prototype:

#define EFI_IFR_FORM_SET_OP 0x0E

typedef struct _EFI_IFR_FORM_SET {
 EFI_IFR_OP_HEADER Header;
 EFI_GUID Guid;
 EFI_STRING_ID FormSetTitle;
 EFI_STRING_ID Help;
 UINT8 Flags;
//EFI_GUID ClassGuid[…];
} EFI_IFR_FORM_SET;

Members:
Header 		The sequence that defines the type of opcode as well as the length of the opcode being defined. Header.OpCode = EFI_IFR_FORM_SET_OP.
Guid 		The unique GUID value associated with this particular form set.
FormSetTitle    The string token reference to the title of this particular form set.
Help            The string token reference to the help of this particular form set.
Flags           Flags which describe additional features of the form set. Bits 0:1 = number of members in ClassGuid. Bits 2:7 = Reserved. Should be set to zero.
ClassGuid       Zero to four class identifiers.

Description
The form set consists of a header and zero or more forms.
```
We didn't declare any class guid in our VFR. But nevertheless in the opcode output you can see that this IFR has one `ClassGuid` equal to `93039971-8545-4b04-b45e-32eb8326040e`. It is assigned by default by the build system if we haven't provided any other GUID for the class. https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Guid/HiiPlatformSetupFormset.h
```
#define EFI_HII_PLATFORM_SETUP_FORMSET_GUID \
  { 0x93039971, 0x8545, 0x4b04, { 0xb4, 0x5e, 0x32, 0xeb, 0x83, 0x26, 0x4, 0xe } }
```
If we would want to declare any other GUID for the class, VFR syntax is:
```
classguid = <...>
```

Next there are couple of `EFI_IFR_DEFAULTSTORE` elements:
```
EFI_IFR_DEFAULTSTORE

Summary:
Provides a declaration for the type of default values that a question can be associated with

Prototype:
#define EFI_IFR_DEFAULTSTORE_OP 0x5c

typedef struct _EFI_IFR_DEFAULTSTORE {
 EFI_IFR_OP_HEADER Header;
 EFI_STRING_ID DefaultName;
 UINT16 DefaultId;
} EFI_IFR_DEFAULTSTORE;

Members
Header 		The sequence that defines the type of opcode as well as the length of the opcode being defined.
		For this tag, Header.OpCode = EFI_IFR_DEFAULTSTORE_OP
DefaultName 	A string token reference for the human readable string associated with the type of default being declared.
DefaultId 	The default identifier, which is unique within the current form set. The default identifier creates a group of defaults

Description:
Declares a class of default which can then have question default values associated with. An EFI_IFR_DEFAULTSTORE with a specified DefaultId must appear in the IFR before it can be referenced by an EFI_IFR_DEFAULT.
```

As we've opened a scope in the `EFI_IFR_FORM_SET`, we need to close it with a `EFI_IFR_END`:
```
EFI_IFR_END

Summary:
End of the current scope.

Prototype:

#define EFI_IFR_END_OP 0x29

typedef struct _EFI_IFR_END {
 EFI_IFR_OP_HEADER Header;
} EFI_IFR_END;

Members:
Header Standard opcode header, where OpCode is EFI_IFR_END_OP.

Description:
Marks the end of the current scope.
```

# Show form

To actually show form we need to utilize `EFI_FORM_BROWSER2_PROTOCOL` function `SendForm`:
```
EFI_FORM_BROWSER2_PROTOCOL.SendForm()

Summary:
Initialize the browser to display the specified configuration forms.

Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_SEND_FORM2) (
 IN CONST EFI_FORM_BROWSER2_PROTOCOL *This,
 IN EFI_HII_HANDLE *Handles,
 IN UINTN HandleCount,
 IN CONST EFI_GUID *FormsetGuid, OPTIONAL
 IN EFI_FORM_ID FormId, OPTIONAL
 IN CONST EFI_SCREEN_DESCRIPTOR *ScreenDimensions, OPTIONAL
 OUT EFI_BROWSER_ACTION_REQUEST *ActionRequest OPTIONAL
 );

Parameters:
This			A pointer to the EFI_FORM_BROWSER2_PROTOCOL instance.
Handles			A pointer to an array of HII handles to display.
HandleCount		The number of handles in the array specified by Handle.
FormsetGuid		This field points to the EFI_GUID which must match the Guid field or one of the
                	elements of the ClassId field in the EFI_IFR_FORM_SET op-code. If FormsetGuid
                	is NULL, then this function will display the form set class
                	EFI_HII_PLATFORM_SETUP_FORMSET_GUID.
FormId			This field specifies the identifier of the form within the form set to render as the first
			displayable page. If this field has a value of 0x0000, then the Forms Browser will
			render the first enabled form in the form set.
ScreenDimensions	Points to recommended form dimensions, including any non-content area, in characters.
ActionRequested		Points to the action recommended by the form.

Description:
This function is the primary interface to the Forms Browser. The Forms Browser displays the forms specified by FormsetGuid and FormId from all of HII handles specified by Handles. If more than one form can be displayed, the Forms Browser will provide some means for the user to navigate between the
forms in addition to that provided by cross-references in the forms themselves.
```

As you can see most of the parameters are optional. So we can call this function as simple as:
```
EFI_STATUS Status;
EFI_FORM_BROWSER2_PROTOCOL* FormBrowser2;
Status = gBS->LocateProtocol(&gEfiFormBrowser2ProtocolGuid, NULL, (VOID**)&FormBrowser2);
if (EFI_ERROR(Status)) {
  return Status;
}

Status = FormBrowser2->SendForm (
                         FormBrowser2,
                         &Handle,
                         1,
                         NULL,
                         0,
                         NULL,
                         NULL
                         );
```

Don't forget to add `gEfiFormBrowser2ProtocolGuid` to the application INF file:
```
[Protocols]
  gEfiFormBrowser2ProtocolGuid
```
And add necessary include:
```
#include <Protocol/FormBrowser2.h>
```

Also at the end of our program we need to remove our HII packages as they are no longer needed:
```
HiiRemovePackages(Handle);
```

If we build and run our application now we would see that Form browser doesn't display anything for us and immediately returns control to the shell.
This is happening because there is nothing to display in our formset. The core element for the formset is form. Therefore let's add the most simple form to our formset `UefiLessonsPkg/HIISimpleForm/Form.vfr`:
```
formset
  guid     = HIISIMPLEFORM_FORMSET_GUID,
  title    = STRING_TOKEN(HIISIMPLEFORM_FORMSET_TITLE),
  help     = STRING_TOKEN(HIISIMPLEFORM_FORMSET_HELP),
  form formid = 1,
    title = STRING_TOKEN(HIISIMPLEFORM_FORMID1_TITLE);
  endform;
endformset;
```
Each form must have at least `formid` and `title`. You can say that they are mandatory fields. `title` would be used for the page title when the form is displayed and `formid` is used to reference form from other code.

Also I want to note that it is possible to write `formid` attribute on a separate string like all the other form attributes:
```
form
  formid = 1,
  title = STRING_TOKEN(HIISIMPLEFORM_FORMID1_TITLE);
endform;
```
But usually `formid` is written at the same string as the `form` keyword. Both syntax are equivalent in VFR.

Don't forget to add new string to the `UefiLessonsPkg/HIISimpleForm/Strings.uni`:
```
...
#string HIISIMPLEFORM_FORMID1_TITLE          #language en-US  "Simple Form"
```

If you build and execute our application now you would get folowing output:
![SimpleForm](SimpleForm.png?raw=true "SimpleForm")

# IFR data parsing

If you look at the `Form.lst` now you would see:
```
//
//  VFR compiler version  2.01 (UEFI 2.4) Developer Build based on Revision: Unknown
//
extern unsigned char HIISimpleFormStrings[];
formset
>00000000: 0E A7 91 CC 2A EF 50 7B B9 4A AB 67 2B 04 F8 BC 13 5E 02 00 03 00 01 71 99 03 93 45 85 04 4B B4 5E 32 EB 83 26 04 0E
>00000027: 5C 06 00 00 00 00
>0000002D: 5C 06 00 00 01 00
  guid = {0xef2acc91, 0x7b50, 0x4ab9, {0xab, 0x67, 0x2b, 0x4, 0xf8, 0xbc, 0x13, 0x5e}},
  title = STRING_TOKEN(0x0002),
  help = STRING_TOKEN(0x0003),
  form
>00000033: 01 86 01 00 04 00
    formid = 1,
    title = STRING_TOKEN(0x0004);
  endform;
>00000039: 29 02
endformset;
>0000003B: 29 02
```

Two IFRs were added. First is `EFI_IFR_FORM`.
```
EFI_IFR_FORM

Summary:
Creates a form.

Prototype:
#define EFI_IFR_FORM_OP 0x01

typedef struct _EFI_IFR_FORM {
 EFI_IFR_OP_HEADER Header;
 EFI_FORM_ID FormId;
 EFI_STRING_ID FormTitle;
} EFI_IFR_FORM;

Members:
Header 		The sequence that defines the type of opcode as well as the length of the opcode being defined. Header.OpCode = EFI_IFR_FORM_OP.
FormId 		The form identifier, which uniquely identifies the form within the form set. The form identifier, along with the device path
		and form set GUID, uniquely identifies a form within a system.
FormTitle 	The string token reference to the title of this particular form.

Description:
A form is the encapsulation of what amounts to a browser page. The header defines a FormId, which is referenced by the form set, among others. It also defines a FormTitle, which is a string to be used as the title for the form.
```
But also as this opcode opens another scope we also now have one more `EFI_IFR_END` opcode.