概述
UEFI用户交互界面的实现涉及到多种不同类型的文件,这里要讲的是VFR(Visual Forms Representation)文件,相比UNI文件它要复杂得多,理解起来也更困难。
UEFI交互界面那些字符串是来自UNI文件的(其实并不是全部来自UNI,也有部分是直接通过代码生成的),而整个窗体的框架部分则是来自VFR文件的。
在UEFI中,构成这样的窗体的组件大致有四种,分别是Strings,Forms,Fonts和Images,如下图所示:
Strings就是UNI文件提供的,Forms就是本文的VFR文件提供的,本文主要介绍的就是这个Forms,以及构成Forms的VFR文件。
Forms:描述了窗体的组织形式,提供了用户交互的方式和交互内容的存储方式等。
Forms是以二进制的形式提供的,这种二进制在EDK框架中被称为IFR(就是上述定义中提到的Internal Forms Representation)。
而IFR通过编译VFR来生成(关于编译工具,在EDK源代码中也有相应的源码,不过没有研究过不确定怎么用)。
因此,总的来说就是,我们通过编写VFR文件来完成对UEFI交互界面的组织形式和交互方式等相关内容的定义。
语法
VFR文件采用的是BNF
语法,与DSC文件采用“#”作为注释标志不同,它使用“//”作为注释标志。另外还有两个关键字是“#define”和“#include”,用来定义和包含头文件的,类似于C的语法。
formset是VFR文件中最重要的部分,用来组成窗体的最常用的结构。其示例如下:
MdeModulePkg/Universal/DriverSampleDxe/Vfr.vfr
formset
guid = {0xcc5ebb4f, 0xf562, 0x11e7, {0x92, 0x11, 0xf4, 0x8c, 0x50, 0x49, 0xe3, 0xa4}},
title = STRING_TOKEN(STR_SAMPLE_FORM_SET_TITLE),
help = STRING_TOKEN(STR_SAMPLE_FORM_SET_HELP),
classguid = EFI_HII_PLATFORM_SETUP_FORMSET_GUID
form formid = 1, title = STRING_TOKEN(STR_SAMPLE_FORM1_TITLE);
…
endform;
endformset;
formset
是关键字,用来标志整个窗体的开始,与标志窗体结束的endformset成对出现。其中各关键字的含义为:
guid
: 标志本formset的GUID值;
title
:在界面中标志本formset的字符串标题;
help
:在界面上显示本formset的帮助信息;
classguid
: 本formaset所挂载页面的GUID值。
在formaset的集合中,可以用“form”和“endform”定义一个完整的页面,比如这篇博客中给出的示例
form formid = ADVANCED_MANAGER_FORM_ID,
title = STRING_TOKEN(STR_EDKII_MENU_TITLE);
subtitle text = STRING_TOKEN(STR_DEVICES_LIST);
label LABEL_DEVICES_LIST;
label LABEL_END;
subtitle text = STRING_TOKEN(STR_EMPTY_STRING);
subtitle text = STRING_TOKEN(STR_EMPTY_STRING);
subtitle text = STRING_TOKEN(STR_EXIT_STRING);
endform;
FormSet 定义
vfrFormSetDefinition ::=
"formset"
"guid" "=" guidDefinition ","
"title" "=" getStringId ","
"help" "=" getStringId ","
{ "classguid" "=" classguidDefinition "," }
{ "class" "=" classDefinition "," }
{ "subclass" "=" subclassDefinition "," }
vfrFormSetList
"endformset" ";"
classguidDefinition ::=
guidDefinition { "|" guidDefinition } { "|" guidDefinition }
classDefinition ::=
validClassNames ( "|" validClassNames )*
validClassNames ::=
"NON_DEVICE"
| "DISK_DEVICE"
| "VIDEO_DEVICE"
| "NETWORK_DEVICE"
| "INPUT_DEVICE"
| "ONBOARD_DEVICE"
| "OTHER_DEVICE"
| Number
subclassDefinition ::=
"SETUP_APPLICATION"
| "GENERAL_APPLICATION"
| "FRONT_PAGE"
| "SINGLE_USE"
| Number
例如
FormSet List
formset内部定义了很多的子选项,称为formset list,也就是上一节formset定义中的vfrFormSetList。
前面的例子中用到的form就是其中的一种
formset list可以有如下的内容:
vfrFormSetList ::=
(
vfrFormDefinition
| vfrFormMapDefinition
| vfrStatementImage
| vfrStatementVarStoreLinear
| vfrStatementVarStoreEfi
| vfrStatementVarStoreNameValue
| vfrStatementDefaultStore
| vfrStatementDisableIfFormSet
| vfrStatementSuppressIfFormSet
| vfrStatementExtension
)*
上述的内容可以分为几种不同的类型:
- 变量定义,如defaultstore,varstore,efivarstore,namevaluevarstore等;
- 控制语句,它会做if判断来确定其包含的formset list是否会被使用,主要有disableif,suppressif,grayoutif和goto语句等(上述的语句只在目前只在form类型语句中见到过,在其外没遇见过,不确定是否可以在它之外);
- form语句,它们是formset里面的主体部分,有form,formmap等;
变量定义
- Default Stores Definition
defaultstore:
vfrStatementDefaultStore ::=
"defaultstore" StringIdentifier ","
"prompt" "=" getStringId
{ "," "attribute" "=" Number } ";"
例:
defaultstore MyStandardDefault,
prompt = STRING_TOKEN(STR_STANDARD_DEFAULT_PROMPT),
attribute = 0x0000; // Default ID: 0000 standard default
defaultstore MyManufactureDefault,
prompt = STRING_TOKEN(STR_MANUFACTURE_DEFAULT_PROMPT),
attribute = 0x0001; // Default ID: 0001 manufacture
- Variable Store Definition
varstore
vfrStatementVarStoreLinear ::=
"varstore"
(
StringIdentifier ","
| "UINT8" ","
| "UINT16" ","
| "UINT32" ","
| "UINT64" ","
| "EFI_HII_DATE" ","
| "EFI_HII_TIME" ","
| "EFI_HII_REF" ","
)
{ "varid" "=" Number "," }
"name" "=" StringIdentifier ","
"guid" "=" guidDefinition ";"
第一个StringIdentifier表示的是类型,第二个表示的是变量名,name和guid连起来就可以表示该特定的变量。
例:
//
// Define a Buffer Storage (EFI_IFR_VARSTORE)
//
varstore DRIVER_SAMPLE_CONFIGURATION, // This is the data structure type
varid = CONFIGURATION_VARSTORE_ID, // Optional VarStore ID
name = MyIfrNVData, // Define referenced name in vfr
guid = IPMI_CONFIG_FORMSET_GUID; // GUID of this buffer storage
它定义的是一个数据结构体变量,类型就是DRIVER_SAMPLE_CONFIGURATION。
MyIfrNVData是变量的名称,后面的VFR表达式中会通过该名称去引用该变量。
结构体原型在这里:DriverSampleDxe/NVDataStruc.h
- EFI Variable Store Definition
efivarstore
vfrStatementVarStoreEfi ::=
"efivarstore"
(
StringIdentifier ","
| "UINT8" ","
| "UINT16" ","
| "UINT32" ","
| "UINT64" ","
| "EFI_HII_DATE" ","
| "EFI_HII_TIME" ","
| "EFI_HII_REF" ","
)
{ "varid" "=" Number "," }
"attribute" "=" Number ( "|" Number )* ","
"name" "=" StringIdentifier ","
"guid" "=" guidDefinition ";"
例:
//
// Define a EFI variable Storage (EFI_IFR_VARSTORE_EFI)
//
efivarstore MY_EFI_VARSTORE_DATA,
attribute = EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE, // EFI variable attribures
name = MyEfiVar,
guid = IPMI_CONFIG_FORMSET_GUID;
这里定义的就是UEFI变量,还可以声明变量的属性。,MY_EFI_VARSTORE_DATA 是结构体名
结构体原型在这里:DriverSampleDxe/NVDataStruc.h
- Variable Name Store Definition
namevaluevarstore
vfrStatementVarStoreNameValue ::=
"namevaluevarstore" StringIdentifier ","
{ "varid" "=" Number "," }
( "name" "=" getStringId "," )+
"guid" "=" guidDefinition ";
例:
//
// Define a Name/Value Storage (EFI_IFR_VARSTORE_NAME_VALUE)
//
namevaluevarstore MyNameValueVar, // Define storage reference name in vfr
name = STRING_TOKEN(STR_NAME_VALUE_VAR_NAME0), // Define Name list of this storage, refer it by MyNameValueVar[0]
name = STRING_TOKEN(STR_NAME_VALUE_VAR_NAME1), // Define Name list of this storage, refer it by MyNameValueVar[1]
name = STRING_TOKEN(STR_NAME_VALUE_VAR_NAME2), // Define Name list of this storage, refer it by MyNameValueVar[2]
guid = IPMI_CONFIG_FORMSET_GUID; // GUID of this Name/Value storage
控制语句
VFR文件中可以包含如下的控制语句
- FormSet DisableIf Definition
DisableIf语句,定义如下:
vfrStatementDisableIfFormSet ::=
"disableif" vfrStatementExpression ";"
vfrFormSetList
"endif" ";"
例:
disableif ideqval MyIfrNVData.SuppressGrayOutSomething == 0x2;
orderedlist
varid = MyIfrNVData.OrderedList,
prompt = STRING_TOKEN(STR_TEST_OPCODE),
help = STRING_TOKEN(STR_TEXT_HELP),
flags = RESET_REQUIRED,
option text = STRING_TOKEN(STR_ONE_OF_TEXT1), value = 3, flags = 0;
option text = STRING_TOKEN(STR_ONE_OF_TEXT2), value = 2, flags = 0;
option text = STRING_TOKEN(STR_ONE_OF_TEXT3), value = 1, flags = 0;
default = {1,2,3},
endlist;
endif;
- FormSet SuppressIf Definition
SuppressIf语句,定义如下:
vfrStatementSuppressIfFormSet ::=
"suppressif" vfrStatementExpression ";"
vfrFormSetList
"endif" ";"
例:
suppressif ideqval MyIfrNVData.BootOrderLarge == 0;
form formid = 2, // SecondSetupPage,
title = STRING_TOKEN(STR_FORM2_TITLE); // note formid is a variable (for readability) (UINT16) - also added Form to the line to signify the Op-Code
……
endif
- Statement GrayOutIf Definition
GrayOutIf语句,定义如下:
vfrStatementGrayOutIfStat ::=
"grayoutif" vfrStatementExpression ";"
( vfrStatementStatList )*
"endif" ";"
例如:
grayoutif match2 (stringref(STRING_TOKEN(STR_PATTERN)), stringref(STRING_TOKEN(STR_STRING)), PERL_GUID);
numeric
varid = MyIfrNVData.Match2,
prompt = STRING_TOKEN(STR_MATCH2_PROMPT),
help = STRING_TOKEN(STR_MATCH2_HELP),
minimum = 0,
maximum = 243,
endnumeric;
endif;
需要注意几点:
-
if条件之后有一个分号;
-
最后有一个endif与之对应;
- Goto Statement Definition
goto 语句
vfrStatementGoto ::=
"goto"
{
(
"devicePath" "=" getStringId ","
"formsetguid" "=" guidDefinition ","
"formid" "=" Number ","
"question" "=" Number ","
)
|
(
"formsetguid" "=" guidDefinition ","
"formid" "=" Number ","
"question" "=" Number ","
)
|
(
"formid" "=" Number ","
"question" "="
(
StringIdentifier ","
| Number ","
)
)
|
(
Number ","
)
}
vfrQuestionHeader
{ "," "flags" "=" vfrGotoFlags }
{ "," "key" "=" Number }
{ "," vfrStatementQuestionOptionList }
";"
vfrGotoFlags ::=
gotoFlagsField ( "|" gotoFlagsField )*
gotoFlagsField ::=
Number
| questionheaderFlagsField
key 是使用的question ID
例:
生成没有key的 EFI_IFR_REF
goto 1,
prompt = STRING_TOKEN(STR_GOTO_PROMPT),
help = STRING_TOKEN(STR_GOTO_HELP);
生成有key的 EFI_IFR_REF
goto 1,
prompt = STRING_TOKEN(STR_GOTO_PROMPT),
help = STRING_TOKEN(STR_GOTO_HELP);
key = 0x1234;
生成 EFI_IFR_REF2
goto
formid = 1,
question = QuesttionRef,
prompt = STRING_TOKEN(STR_GOTO_PROMPT),
help = STRING_TOKEN(STR_GOTO_HELP);
生成 EFI_IFR_REF3
goto
formsetguid = FORMSET_GUID,
formid = 1,
question = QuesttionRef,
prompt = STRING_TOKEN(STR_GOTO_PROMPT),
help = STRING_TOKEN(STR_GOTO_HELP);
生成 EFI_IFR_REF4
goto
devicepath = STRING_TOKEN(STR_DEVICE_PATH),
formsetguid = FORMSET_GUID,
formid = 1,
question = QuesttionRef,
prompt = STRING_TOKEN(STR_GOTO_PROMPT),
help = STRING_TOKEN(STR_GOTO_HELP);
生成EFI_IFR_REF5 没有 varid
goto
prompt = STRING_TOKEN(STR_GOTO_PROMPT),
help = STRING_TOKEN(STR_GOTO_HELP);
生成EFI_IFR_REF5 有 varid
goto
varid = MySTestData.mFieldRef,
prompt = STRING_TOKEN(STR_GOTO_PROMPT),
help = STRING_TOKEN STR_GOTO_HELP);
default = FID;QID;GuidValue;STRING_TOKEN(STR_DEVICE_PATH),
;
生成FI_IFR_REF 有 option code
goto 1,
prompt = STRING_TOKEN(STR_GOTO_PROMPT),
help = STRING_TOKEN(STR_GOTO_HELP),
refresh interval = 3
;
需要注意
- Vfr不支持枚举,所以包含的.h中不能有枚举
- 不支持数组大小做算数运算;
- 包含的.h内容都会放到vfr.i 中,所以,包含的头文件一定要单一;
本文主要参考自《edk-ii-vfr-specification.pdf》和《Tiano+Setup+Design+Guide.pdfTiano+Setup+Design+Guide.pdf》,详细可以查看源文档
也可以查看这里:EdkCompatibilityPkg/Sample/Tools/Source/VfrCompile/VfrCompile.g