UEFI实战HII之FrontPage
Posted jiangwei0512
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UEFI实战HII之FrontPage相关的知识,希望对你有一定的参考价值。
写在前面
UEFI有自己的用户交互界面,它的实现基础被称为HII(Human Interface Infrastructure),本文是一系列介绍HII实现的第一篇。这里从开源EDK代码中的界面(称为Front Page)入手,介绍它的实现,并进一步说明整个HII。
之前已经写过一系列跟Setup相关的文档,内容有重复及补充。
入口说明
Front Page的实现代码可以在https://gitee.com/jiangwei0512/edk2-beni)找到,编译执行之后的结果大致如下:
它对应的是一个启动项程序UiApp,模块代码是MdeModulePkg\\Application\\UiApp\\UiApp.inf,该模块的入口函数是InitializeUserInterface()
(位于MdeModulePkg\\Application\\UiApp\\FrontPage.c)。
初始化的大致流程如下:
上述流程中最主要的是两个部分,”初始化Front Page“和”显示Front Page“,对应到两个函数InitializeFrontPage()
和CallFrontPage()
,它们可以连起来看,主要做的事情就是两个:一个是准备素材,这里的素材指的是uni文件、vfr文件等表示的HII数据;另一个是显示这些素材,它通过一个EFI_FORM_BROWSER2_PROTOCOL
来完成,这个Protocol提供两个接口,SendForm()
用来显示配置好的HII,BrowserCallback()
会被回调函数调用,用来获取和设置界面元素。
界面元素
在HII中构成一个界面的元素大致有四种,分别是字符串(Strings)、结构(Forms)、字体(Fonts)和图像(Images),如下图所示(最右边的Questions等是结构的组成部分,这里可以暂时不管,后续会介绍):
字符串通过uni文件产生,结构通过vfr文件产生,字体暂不介绍,而图像则没有特别好介绍的。本例的Front Page中使用了结构、字符串和字体,图像则没有使用到。
元素更新
元素更新主要发生在InitializeFrontPage()
函数中,对应的代码:
//
//Updata Front Page banner strings
//
UpdateFrontPageBannerStrings ();
//
// Update front page menus.
//
UpdateFrontPageForm();
- 前者
UpdateFrontPageBannerStrings()
主要是获取标记①(Token)对应的字符串的值并设置到对应的标记②中,它初始化Front Page的上半区静态部分:
以代码为例的话就像下面的那样:
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);
这里的标记STR_FRONT_PAGE_COMPUTER_MODEL
出现了两次,虽然名字相同,但是是来自不同的文件,分别是uni文件中的:
#string STR_FRONT_PAGE_COMPUTER_MODEL #language en-US ""
#language fr-FR ""
和来自vfr文件中的:
banner
title = STRING_TOKEN(STR_FRONT_PAGE_COMPUTER_MODEL),
line 1,
align left;
标记①的操作通过HiiGetString()
获取字符串,标记②的操作通过HiiSetString()
设置字符串。不过两者本来就需要是一致的,所以也不需要特意去区分。
- 后者
UpdateFrontPageForm()
更新其它动态的部分,而动态的部分通过vfr文件查看可以看到它们位于两个操作码之间:
label LABEL_FRANTPAGE_INFORMATION;
//
// This is where we will dynamically add a Action type op-code to show
// the platform information.
//
label LABEL_END;
对应的代码:
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; // 对应vfr中的LABEL_FRANTPAGE_INFORMATION
//
// 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; // 对应vfr中的LABEL_END
//
//Updata Front Page form
//
UiCustomizeFrontPage (
gFrontPagePrivate.HiiHandle,
StartOpCodeHandle
);
HiiUpdateForm (
gFrontPagePrivate.HiiHandle,
&mFrontPageGuid,
FRONT_PAGE_FORM_ID,
StartOpCodeHandle,
EndOpCodeHandle
);
HiiFreeOpCodeHandle (StartOpCodeHandle);
HiiFreeOpCodeHandle (EndOpCodeHandle);
这里的两个部分(StartOpCodeHandle
和EndOpCodeHandle
)组合构成了Front Page剩余的部分,其过程如下:
- 通过函数
HiiAllocateOpCodeHandle()
创建两个OpCodeHandle,它们对应的是同一个结构体:
typedef struct
UINT8 *Buffer;
UINTN BufferSize;
UINTN Position;
HII_LIB_OPCODE_BUFFER;
创建好之后的结构体中Buffer
是一个大小为0x200字节的空间;BufferSize
就是0x200;Position
初始化为0,它相当于存放其它操作码的容器。
- 创建两个OpCode,会使用到前面创建的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;
HiiCreateGuidOpCode()
的第一个参数就是OpCodeHandle;第二个参数是一个GUID;第三个参数是可选的,可以是NULL;第四个参数是组件元素结构体的大小。本例创建了两个EFI_IFR_GUID_LABEL
结构体(跟vfr文件中的label
对应),它也是HiiCreateGuidOpCode()
的返回值,其结构如下:
///
/// 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;
后面的两句代码用来初始化EFI_IFR_GUID_LABEL
结构体的后两个参数,其中Number
就对应到vfr文件中的label
名,本例中分别是LABEL_FRANTPAGE_INFORMATION
和LABEL_END
。
- 对
OpCode
的自定义,其实就是在StartOpCodeHandle
和EndOpCodeHandle
之间创建自定义的元素。 - 更新Front Page结构,更新的部分就是前面代码中增加的自定义元素。
- 释放资源。
这里最重要的是第3步,对应如下的函数:
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);
基本上图中显示的部分都有对应的函数,但是底部黑色部分并没有,它们是根据特定情况而产生的,比如<Enter>= Select Entry
是因为UiCreateLanguageMenu()
而显示的,而↑↓=Move Highlight
只要有创建上述的元素就会有显示。动作显示的组件有一个包含的关系,如下图所示: