[UEFI Combat] FrontPage of HII

written in front
UEFI has its own user interface, and its implementation basis is called HII (Human Interface Infrastructure). This article is the first in a series of introductions to HII implementation. Here we start with the interface (called Front Page) in the open source EDK code, introduce its implementation, and further explain the entire HII.

I have written a series of documents related to Setup before, and the content is repeated and supplemented.

Entry Description
The implementation code of Front Page can be found at https://gitee.com/jiangwei0512/edk2-beni), and the results after compilation and execution are roughly as follows:

It corresponds to a startup item program UiApp, the module code is MdeModulePkg\Application\UiApp\UiApp.inf, and the entry function of this module is InitializeUserInterface() (located in MdeModulePkg\Application\UiApp\FrontPage.c).

The general flow of initialization is as follows:

Enter the UI
Cleanup after exiting the UI
Initialize graphics mode parameters
Disable software watchdog
clear screen
install fonts
Initialize the HII string
Set the terminal mode, the parameters come from the first step
Enter the UI entry
UI entry
Cleanup after exiting Front Page
UI entry
Initialize the boot device if necessary
Refresh startup items
Logo operation
Initialize Front Page
Show Front Page
The two main parts in the above process are “Initializing Front Page” and “Displaying Front Page”, which correspond to two functions InitializeFrontPage() and CallFrontPage(). They can be linked together to see that there are two main things to do: One is to prepare materials, where the materials refer to HII data represented by uni files, vfr files, etc.; the other is to display these materials, which is completed through an EFI_FORM_BROWSER2_PROTOCOL. This Protocol provides two interfaces, and SendForm() is used to display the configuration. Good HII, BrowserCallback() will be called by the callback function to get and set interface elements.

interface elements
There are roughly four elements that constitute an interface in HII, namely strings (Strings), structures (Forms), fonts (Fonts) and images (Images), as shown in the figure below (Questions on the far right are the composition of the structure part, which can be ignored here for the time being, and will be introduced later):

Strings are generated through uni files, structures are generated through vfr files, fonts are not introduced yet, and images are not particularly easy to introduce. The Front Page in this example uses structures, strings, and fonts, but images are not used.

element update
Element update mainly occurs in the InitializeFrontPage() function, the corresponding code:

//
//Updata Front Page banner strings
//
UpdateFrontPageBannerStrings();

//
// Update front page menus.
//
UpdateFrontPageForm();

The former UpdateFrontPageBannerStrings() is mainly to obtain the value of the string corresponding to the tag ① (Token) and set it to the corresponding tag ②. It initializes the static part of the upper half of the Front Page:

Taking the code as an example, it looks like the following:

NewString = HiiGetString (gFrontPagePrivate.HiiHandle, STRING_TOKEN (STR_FRONT_PAGE_COMPUTER_MODEL), NULL);
UiCustomizeFrontPageBanner (1, TRUE, & NewString);
HiiSetString (gFrontPagePrivate.HiiHandle, STRING_TOKEN (STR_FRONT_PAGE_COMPUTER_MODEL), NewString, NULL);
FreePool(NewString);

The mark STR_FRONT_PAGE_COMPUTER_MODEL here appears twice, although the name is the same, but it comes from different files, which are in the uni file:

#string STR_FRONT_PAGE_COMPUTER_MODEL #language en-US “”
#language fr-FR “”

and from the vfr file:

banner
  title = STRING_TOKEN(STR_FRONT_PAGE_COMPUTER_MODEL),
  line 1,
  align left;

The operation marked ① obtains the string through HiiGetString(), and the operation marked ② sets the string through HiiSetString(). However, the two need to be consistent, so there is no need to distinguish them.

The latter UpdateFrontPageForm() updates other dynamic parts, and the dynamic parts can be seen through the vfr file to see that they are located between the two opcodes:
label LABEL_FRANTPAGE_INFORMATION;
//
// This is where we will dynamically add an Action type op-code to show
// the platform information.
//
label LABEL_END;

Corresponding code:

VOID
UpdateFrontPageForm (
VOID
)
{
VOID *StartOpCodeHandle;
VOID *EndOpCodeHandle;
EFI_IFR_GUID_LABEL *StartGuidLabel;
EFI_IFR_GUID_LABEL *EndGuidLabel;

//
//Allocate space for creation of UpdateData Buffer
//
StartOpCodeHandle = HiiAllocateOpCodeHandle();
ASSERT (StartOpCodeHandle != NULL);

EndOpCodeHandle = HiiAllocateOpCodeHandle();
ASSERT(EndOpCodeHandle != NULL);
//
// Create Hii Extend Label OpCode as the start opcode
//
StartGuidLabel = (EFI_IFR_GUID_LABEL *) HiiCreateGuidOpCode (StartOpCodeHandle, &gEfiIfrTianoGuid, NULL, sizeof (EFI_IFR_GUID_LABEL));
StartGuidLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;
StartGuidLabel->Number = LABEL_FRANTPAGE_INFORMATION; // corresponding to LABEL_FRANTPAGE_INFORMATION in vfr
//
// Create Hii Extend Label OpCode as the end opcode
//
EndGuidLabel = (EFI_IFR_GUID_LABEL *) HiiCreateGuidOpCode (EndOpCodeHandle, &gEfiIfrTianoGuid, NULL, sizeof (EFI_IFR_GUID_LABEL));
EndGuidLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;
EndGuidLabel->Number = LABEL_END; // corresponds to LABEL_END in vfr

//
//Updata Front Page form
//
UiCustomizeFrontPage (
gFrontPagePrivate.HiiHandle,
StartOpCodeHandle
);

HiiUpdateForm (
gFrontPagePrivate.HiiHandle,
&mFrontPageGuid,
FRONT_PAGE_FORM_ID,
StartOpCodeHandle,
EndOpCodeHandle
);

HiiFreeOpCodeHandle (StartOpCodeHandle);
HiiFreeOpCodeHandle (EndOpCodeHandle);
}

The combination of the two parts here (StartOpCodeHandle and EndOpCodeHandle) constitutes the rest of the Front Page, and the process is as follows:

Create two OpCodeHandles through the function HiiAllocateOpCodeHandle(), which correspond to the same structure:
typedef struct {
UINT8 *Buffer;
UINTN BufferSize;
UINTN Position;
} HII_LIB_OPCODE_BUFFER;

In the created structure, Buffer is a space with a size of 0x200 bytes; BufferSize is 0x200; Position is initialized to 0, which is equivalent to a container for storing other opcodes.

Create two OpCode, will use the previously created OpCodeHandle:
StartGuidLabel = (EFI_IFR_GUID_LABEL *) HiiCreateGuidOpCode (StartOpCodeHandle, &gEfiIfrTianoGuid, NULL, sizeof (EFI_IFR_GUID_LABEL));
StartGuidLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;
StartGuidLabel->Number = LABEL_FRANTPAGE_INFORMATION;
1
2
3
The first parameter of HiiCreateGuidOpCode() is OpCodeHandle; the second parameter is a GUID; the third parameter is optional and can be NULL; the fourth parameter is the size of the component element structure. This example creates two EFI_IFR_GUID_LABEL structures (corresponding to the label in the vfr file), which are also the return value of HiiCreateGuidOpCode(), and its structure is as follows:

///
/// Label opcode.
///
typedef struct _EFI_IFR_GUID_LABEL {
EFI_IFR_OP_HEADER Header;
///
/// EFI_IFR_TIANO_GUID.
///
EFI_GUID Guid;
///
/// EFI_IFR_EXTEND_OP_LABEL.
///
UINT8 ExtendOpCode;
///
/// Label Number.
///
UINT16 Number;
} EFI_IFR_GUID_LABEL;

The following two lines of code are used to initialize the last two parameters of the EFI_IFR_GUID_LABEL structure, where Number corresponds to the label name in the vfr file, which are LABEL_FRANTPAGE_INFORMATION and LABEL_END in this example.

Customization of OpCode is actually to create custom elements between StartOpCodeHandle and EndOpCodeHandle.
Update the Front Page structure, the updated part is the custom element added in the previous code.
Release resources.
The most important thing here is step 3, which corresponds to the following functions:

VOID
UiCustomizeFrontPage (
IN EFI_HII_HANDLE HiiHandle,
IN VOID *StartOpCodeHandle
)
{
//
// Create “Select Language” menu with Oneof opcode.
//
UiCreateLanguageMenu (HiiHandle, StartOpCodeHandle);

//
// Create empty line.
//
UiCreateEmptyLine(HiiHandle, StartOpCodeHandle);

//
// Find third party drivers which need to be shown in the front page.
//
UiListThirdPartyDrivers (HiiHandle, &gEfiIfrFrontPageGuid, NULL, StartOpCodeHandle);

//
// Create empty line.
//
UiCreateEmptyLine(HiiHandle, StartOpCodeHandle);

//
// Create “Continue” menu.
//
UiCreateContinueMenu(HiiHandle, StartOpCodeHandle);

//
// Create reset menu.
//
UiCreateResetMenu(HiiHandle, StartOpCodeHandle);
}

Basically, the parts shown in the figure have corresponding functions, but the black part at the bottom does not. They are generated according to specific situations. For example, = Select Entry is displayed because of UiCreateLanguageMenu(), and ↑↓=Move Highlight only needs to have Create the above elements and it will be displayed. The components displayed by the action have a containment relationship, as shown in the following diagram:

start tag
Single box
Several single options
end tag
Blank lines
Interface provided by third-party drivers
Blank lines
Continue action bar
Reset action bar
Subsequent sections describe how to create dynamically displayed parts of the diagram.

create menu
The specific code of UiCreateLanguageMenu():

VOID
UiCreateLanguageMenu (
IN EFI_HII_HANDLE HiiHandle,
IN VOID *StartOpCodeHandle
)
{
CHAR8 *LangCode;
CHAR8 *Lang;
UINTN LangSize;
CHAR8 *CurrentLang;
UINTN OptionCount;
CHAR16 *StringBuffer;
VOID *OptionsOpCodeHandle;
UINTN StringSize;
EFI_STATUS Status;
EFI_HII_STRING_PROTOCOL *HiiString;

Lang = NULL;
StringBuffer = NULL;

//
// Init OpCode Handle and Allocate space for creation of UpdateData Buffer
//
OptionsOpCodeHandle = HiiAllocateOpCodeHandle();
ASSERT (OptionsOpCodeHandle != NULL);

GetEfiGlobalVariable2 (L”PlatformLang”, (VOID**) &CurrentLang, NULL);

//
// Get Support language list from variable.
//
GetEfiGlobalVariable2 (L”PlatformLangCodes”, (VOID**) &gLanguageString, NULL);
if (gLanguageString == NULL) {
gLanguageString = AllocateCopyPool(
AsciiStrSize ((CHAR8 *) PcdGetPtr (PcdUefiVariableDefaultPlatformLangCodes)),
(CHAR8 *) PcdGetPtr (PcdUefiVariableDefaultPlatformLangCodes)
);
ASSERT(gLanguageString != NULL);
}

if (gLanguageToken == NULL) {
//
// Count the language list number.
//
LangCode = gLanguageString;
Lang = AllocatePool(AsciiStrSize(gLanguageString));
ASSERT (Lang != NULL);

OptionCount = 0;
while (*LangCode != 0) {
  GetNextLanguage ( &LangCode, Lang);
  OptionCount++;
}

//
// Allocate extra 1 as the end tag.
//
gLanguageToken = AllocateZeroPool ((OptionCount + 1) * sizeof (EFI_STRING_ID));
ASSERT(gLanguageToken != NULL);

Status = gBS->LocateProtocol ( &gEfiHiiStringProtocolGuid, NULL, (VOID **) &HiiString);
ASSERT_EFI_ERROR(Status);

LangCode = gLanguageString;
OptionCount = 0;
while (*LangCode != 0) {
  GetNextLanguage ( &LangCode, Lang);

  StringSize = 0;
  Status = HiiString->GetString (HiiString, Lang, HiiHandle, PRINTABLE_LANGUAGE_NAME_STRING_ID, StringBuffer, & amp;StringSize, NULL);
  if (Status == EFI_BUFFER_TOO_SMALL) {
    StringBuffer = AllocateZeroPool(StringSize);
    ASSERT (StringBuffer != NULL);
    Status = HiiString->GetString (HiiString, Lang, HiiHandle, PRINTABLE_LANGUAGE_NAME_STRING_ID, StringBuffer, & amp;StringSize, NULL);
    ASSERT_EFI_ERROR(Status);
  }

  if (EFI_ERROR (Status)) {
    LangSize = AsciiStrSize(Lang);
    StringBuffer = AllocatePool (LangSize * sizeof (CHAR16));
    ASSERT (StringBuffer != NULL);
    AsciiStrToUnicodeStrS (Lang, StringBuffer, LangSize);
  }

  ASSERT (StringBuffer != NULL);
  gLanguageToken[OptionCount] = HiiSetString (HiiHandle, 0, StringBuffer, NULL);
  FreePool(StringBuffer);

  OptionCount++;
}

}

ASSERT(gLanguageToken != NULL);
LangCode = gLanguageString;
OptionCount = 0;
if (Lang == NULL) {
Lang = AllocatePool(AsciiStrSize(gLanguageString));
ASSERT (Lang != NULL);
}
while (*LangCode != 0) {
GetNextLanguage ( &LangCode, Lang);

if (CurrentLang != NULL & amp; & amp; AsciiStrCmp (Lang, CurrentLang) == 0) {
  HiiCreateOneOfOptionOpCode (
    OptionsOpCodeHandle,
    gLanguageToken[OptionCount],
    EFI_IFR_OPTION_DEFAULT,
    EFI_IFR_NUMERIC_SIZE_1,
    (UINT8) OptionCount
    );
  gCurrentLanguageIndex = (UINT8) OptionCount;
} else {
  HiiCreateOneOfOptionOpCode (
    OptionsOpCodeHandle,
    gLanguageToken[OptionCount],
    0,
    EFI_IFR_NUMERIC_SIZE_1,
    (UINT8) OptionCount
    );
}

OptionCount++;

}

if (CurrentLang != NULL) {
FreePool(CurrentLang);
}
FreePool (Lang);

HiiCreateOneOfOpCode (
StartOpCodeHandle,
FRONT_PAGE_KEY_LANGUAGE,
0,
0,
STRING_TOKEN (STR_LANGUAGE_SELECT),
STRING_TOKEN (STR_LANGUAGE_SELECT_HELP),
EFI_IFR_FLAG_CALLBACK,
EFI_IFR_NUMERIC_SIZE_1,
OptionsOpCodeHandle,
NULL
);
}

Most of the code here is just the data that needs to be used when creating options, and you can pay special attention to it. There are only two important steps here:

Create a new HII_LIB_OPCODE_BUFFER to store the single option, and put the single option into HII_LIB_OPCODE_BUFFER through HiiCreateOneOfOptionOpCode() function;
Construct the HII_LIB_OPCODE_BUFFER storing the radio options into a radio box and store it in the HII_LIB_OPCODE_BUFFER of the upper layer.
create blank line
The blank line corresponds to an EFI_IFR_SUBTITLE_OP (Subtitle statement), and the corresponding structure:

typedef struct _EFI_IFR_SUBTITLE {
EFI_IFR_OP_HEADER Header;
EFI_IFR_STATEMENT_HEADER Statement;
UINT8 Flags;
} EFI_IFR_SUBTITLE;

But there is no actual data, it becomes a blank line, note that the parameters at the time of creation are all 0:

VOID
UiCreateEmptyLine (
IN EFI_HII_HANDLE HiiHandle,
IN VOID *StartOpCodeHandle
)
{
HiiCreateSubTitleOpCode (StartOpCodeHandle, STRING_TOKEN (STR_NULL_STRING), 0, 0, 0);
}

Create interfaces used by third-party drivers
Implementation process:

Find all HII options installed in the system
Allocate space for each option
Initialize each option
Create opcodes for each option
The final operation is also to create the opcode:

HiiCreateGotoExOpCode (
  StartOpCodeHandle,
  0,
  gHiiDriverList[Index].PromptId,
  gHiiDriverList[Index].HelpId,
  0,
  (EFI_QUESTION_ID) (Index + FRONT_PAGE_KEY_DRIVER),
  0,
   &gHiiDriverList[Index].FormSetGuid,
  gHiiDriverList[Index].DevicePathId
);

The corresponding opcodes may be EFI_IFR_REF_OP, EFI_IFR_REF2_OP, EFI_IFR_REF3_OP, and EFI_IFR_REF4_OP, depending on the parameter value passed in.

Create Continue/reset menu
Both correspond to the EFI_IFR_ACTION_OP opcode, and the corresponding implementation:

/**
Create continue menu in the front page.

@param[in] HiiHandle The hii handle for the Uiapp driver.
@param[in] StartOpCodeHandle The opcode handle to save the new opcode.

**/
VOID
UiCreateContinueMenu (
IN EFI_HII_HANDLE HiiHandle,
IN VOID *StartOpCodeHandle
)
{
HiiCreateActionOpCode (
StartOpCodeHandle,
FRONT_PAGE_KEY_CONTINUE,
STRING_TOKEN (STR_CONTINUE_PROMPT),
STRING_TOKEN (STR_CONTINUE_PROMPT),
EFI_IFR_FLAG_CALLBACK,
0
);
}

/**
Create Reset menu in the front page.

@param[in] HiiHandle The hii handle for the Uiapp driver.
@param[in] StartOpCodeHandle The opcode handle to save the new opcode.

**/
VOID
UiCreateResetMenu (
IN EFI_HII_HANDLE HiiHandle,
IN VOID *StartOpCodeHandle
)
{
HiiCreateActionOpCode (
StartOpCodeHandle,
FRONT_PAGE_KEY_RESET,
STRING_TOKEN (STR_RESET_STRING),
STRING_TOKEN (STR_RESET_STRING),
EFI_IFR_FLAG_CALLBACK,
0
);
}

As you can see from the code, the difference is only in the display and QuestionId. The latter judges its value in the callback function and performs different operations. The code is in UiSupportLibCallbackHandler(), and part of the code is as follows:

switch (QuestionId) {
case FRONT_PAGE_KEY_CONTINUE:
  //
  // This is the continue - clear the screen and return an error to get out of FrontPage loop
  //
  *ActionRequest = EFI_BROWSER_ACTION_REQUEST_EXIT;
  break;

case FRONT_PAGE_KEY_LANGUAGE:
  *Status = LanguageChangeHandler(Value);
  break;

case FRONT_PAGE_KEY_RESET:
  //
  // Reset
  //
  gRT->ResetSystem (EfiResetCold, EFI_SUCCESS, 0, NULL);
  *Status = EFI_UNSUPPORTED;

default:
  break;
}