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:
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.
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.
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:
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.