UEFI实战HII之vfr文件

Posted jiangwei0512

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UEFI实战HII之vfr文件相关的知识,希望对你有一定的参考价值。

vfr文件

HII的实现涉及到多种不同类型的文件,vfr文件是其中最重要的一种,它构成了界面的结构样式。本文主要参考自《edk-ii-vfr-specification.pdf》,后面简称为参考文档。

UEFI代码操作UI界面,并不是直接使用vfr文件,而是称为IFR(Internal Forms Representation)的二进制,vfr文件只是IFR的字符串表示,方便通过可识别的字符串作为代码来编写界面,然后通过VFR编译器编译出IFR二进制,最终给UEFI代码使用。

以前面出现过的Front Page为例,图中界面的结构依赖于vfr文件MdeModulePkg\\Application\\UiApp\\FrontPageVfr.Vfr,它构建了Front Page页面的骨架,该文件内容如下:

#define FORMSET_GUID   0x9e0c30bc, 0x3f06, 0x4ba6, 0x82, 0x88, 0x9, 0x17, 0x9b, 0x85, 0x5d, 0xbe 

#define FRONT_PAGE_FORM_ID             0x1000

#define LABEL_FRANTPAGE_INFORMATION    0x1000
#define LABEL_END                      0xffff

formset
  guid     = FORMSET_GUID,
  title    = STRING_TOKEN(STR_FRONT_PAGE_TITLE),
  help     = STRING_TOKEN(STR_EMPTY_STRING ),
  classguid = FORMSET_GUID,

  form formid = FRONT_PAGE_FORM_ID,
       title  = STRING_TOKEN(STR_FRONT_PAGE_TITLE);

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

    banner
      title = STRING_TOKEN(STR_FRONT_PAGE_CPU_MODEL),
      line  2,
      align left;

    banner
      title = STRING_TOKEN(STR_FRONT_PAGE_CPU_SPEED),
      line  2,
      align right;

    banner
      title = STRING_TOKEN(STR_FRONT_PAGE_Bios_VERSION),
      line  3,
      align left;

    banner
      title = STRING_TOKEN(STR_FRONT_PAGE_MEMORY_SIZE),
      line  3,
      align right;

    banner
      title = STRING_TOKEN(STR_CUSTOMIZE_BANNER_LINE4_LEFT),
      line  4,
      align left;

    banner
      title = STRING_TOKEN(STR_CUSTOMIZE_BANNER_LINE4_RIGHT),
      line  4,
      align right;

    banner
      title = STRING_TOKEN(STR_CUSTOMIZE_BANNER_LINE5_LEFT),
      line  5,
      align left;

    banner
      title = STRING_TOKEN(STR_CUSTOMIZE_BANNER_LINE5_RIGHT),
      line  5,
      align right;

    label LABEL_FRANTPAGE_INFORMATION;
    //
    // This is where we will dynamically add a Action type op-code to show
    // the platform information.
    //
    label LABEL_END;

  endform;

它构成的结构基本样式如下:

STR_FRONT_PAGE_COMPUTER_MODEL
STR_FRONT_PAGE_CPU_MODEL								STR_FRONT_PAGE_CPU_SPEED
STR_FRONT_PAGE_BIOS_VERSION								STR_FRONT_PAGE_MEMORY_SIZE
STR_CUSTOMIZE_BANNER_LINE4_LEFT							STR_CUSTOMIZE_BANNER_LINE4_RIGHT
STR_CUSTOMIZE_BANNER_LINE5_LEFT							STR_CUSTOMIZE_BANNER_LINE5_RIGHT
LABEL_FRANTPAGE_INFORMATION
LABEL_END

对应到Front Page界面中(第四、五行没有使用):

红色字体部分是vfr文件中定义的标记,而显示的字符串有一部分是在uni文件中定义的,而红框部分是通过代码实现的,所以vfr文件构成了界面的静态框架,而代码又可以通过vfr文件中定义的标记来动态修改。

vfr文件通过特定的VFR编译器编译,最终得到的是中间文件。该中间文件是一个c文件(FrontPageVfr.c)或者hpk文件,里面就是一个变量用来表示VFR资源:

unsigned char FrontPageVfrBin[] = 
  // ARRAY LENGTH,0x143加上下述的头部长度,就是0x147
  0x47,  0x01,  0x00,  0x00,  

  // PACKAGE HEADER,对应的是EFI_HII_FORM_PACKAGE_HDR,0x13F加上下述的头部长度,就是0x143,占据3个字节;第4个字节0x02表示HII类型Form
  0x43,  0x01,  0x00,  0x02,  

  // PACKAGE DATA,319个字节,十六进制就是0x13F,注释已经加上

  // 第1个操作码,对应结构体EFI_IFR_FORM_SET
  0x0E,  // EFI_IFR_FORM_SET_OP,它的值就是0xE
  0xA7,  // 前面7位表示长度,即0x27=39个字节,总长度到第二个FORMSET_GUID为止刚好39个字节;第8位表示的是scope,该位为1则表示开始一个新的scope
  // Guid: FORMSET_GUID
  0xBC,  0x30,  0x0C,  0x9E,  0x06,  0x3F,  0xA6,  0x4B,  0x82,  0x88,  0x09,  0x17,  0x9B,  0x85,  0x5D,  0xBE,  
  0x02,  0x00,  // FormSetTitle:字符串Token,在AutoGen中定义,对应的是STR_FRONT_PAGE_TITLE,值就是0x0002
  0x0C,  0x00,  // Help:字符串Token,在AutoGen中定义,对应的是STR_EMPTY_STRING,值就是0x000C
  0x01,	// Flags:
  // ClassGuid: FORMSET_GUID
  0xBC,  0x30,  0x0C,  0x9E,  0x06,  0x3F,  0xA6,  0x4B,  0x82,  0x88,  0x09,  0x17,  0x9B,  0x85,  0x5D,  0xBE,

  // 第2个操作码,对应结构体EFI_IFR_DEFAULTSTORE
  0x5C,  // 操作码EFI_IFR_DEFAULTSTORE_OP
  0x06,  // 长度6个字节
  0x00,  0x00,  // DefaultName:字符串Token,似乎并不存在
  0x00,  0x00,  // DefaultId:表示EFI_HII_DEFAULT_CLASS_STANDARD

  // 第3个操作码,对应结构体EFI_IFR_DEFAULTSTORE
  0x5C,  // 操作码EFI_IFR_DEFAULTSTORE_OP
  0x06,  // 长度6个字节
  0x00,  0x00,  // DefaultName:字符串Token,似乎并不存在
  0x01,  0x00,  // DefaultId:表示EFI_HII_DEFAULT_CLASS_MANUFACTURING
  // 前面两个操作码并没有在vfr文件中声明,但是却创建了。

  // 第4个操作码,对应结构体EFI_IFR_FORM
  0x01,  // 操作码示EFI_IFR_FORM_OP
  0x86,  // 长度6个字节,新建scope
  0x00,  0x10,  // FormId:对应FRONT_PAGE_FORM_ID
  0x02,  0x00,  // FormTitle:对应字符串Token,STR_FRONT_PAGE_TITLE

  // 第5个操作码,对应结构体EFI_IFR_GUID_BANNER
  0x5F,  // 操作码示EFI_IFR_GUID_OP
  0x18,  // 长度24个字节,其中16个是GUID
  // EFI_IFR_TIANO_GUID,表示GUIDed opcodes defined for EDKII implementation,定义在MdeModulePkg\\Include\\Guid\\MdeModuleHii.h
  0x35,  0x17,  0x0B,  0x0F,  0xA0,  0x87,  0x93,  0x41,  0xB2,  0x66,  0x53,  0x8C,  0x38,  0xAF, 0x48,  0xCE,  
  0x01,  // ExtendOpCode,0x01表示EFI_IFR_EXTEND_OP_BANNER
  0x03,  0x00,  // Title,对应字符串Token,0x0003对应的是STR_FRONT_PAGE_COMPUTER_MODEL
  0x01,  0x00,  // LineNumber
  0x00,  // Alignment
    
  0x5F,  0x18,  0x35,  0x17,  0x0B,  0x0F,  0xA0,  0x87,  0x93,  0x41,  0xB2,  0x66,  0x53,  0x8C,  0x38,  0xAF,  
  0x48,  0xCE,  0x01,  0x04,  0x00,  0x02,  0x00,  0x00,  
    
  0x5F,  0x18,  0x35,  0x17,  0x0B,  0x0F,  0xA0,  0x87,  0x93,  0x41,  0xB2,  0x66,  0x53,  0x8C,  0x38,  0xAF,  
  0x48,  0xCE,  0x01,  0x05,  0x00,  0x02,  0x00,  0x02,  
    
  0x5F,  0x18,  0x35,  0x17,  0x0B,  0x0F,  0xA0,  0x87,  0x93,  0x41,  0xB2,  0x66,  0x53,  0x8C,  0x38,  0xAF,  
  0x48,  0xCE,  0x01,  0x07,  0x00,  0x03,  0x00,  0x00,  
    
  0x5F,  0x18,  0x35,  0x17,  0x0B,  0x0F,  0xA0,  0x87,  0x93,  0x41,  0xB2,  0x66,  0x53,  0x8C,  0x38,  0xAF,  
  0x48,  0xCE,  0x01,  0x06,  0x00,  0x03,  0x00,  0x02,  
    
  0x5F,  0x18,  0x35,  0x17,  0x0B,  0x0F,  0xA0,  0x87,  0x93,  0x41,  0xB2,  0x66,  0x53,  0x8C,  0x38,  0xAF,  
  0x48,  0xCE,  0x01,  0x0E,  0x00,  0x04,  0x00,  0x00,  
    
  0x5F,  0x18,  0x35,  0x17,  0x0B,  0x0F,  0xA0,  0x87,  0x93,  0x41,  0xB2,  0x66,  0x53,  0x8C,  0x38,  0xAF,  
  0x48,  0xCE,  0x01,  0x0F,  0x00,  0x04,  0x00,  0x02,  
    
  0x5F,  0x18,  0x35,  0x17,  0x0B,  0x0F,  0xA0,  0x87,  0x93,  0x41,  0xB2,  0x66,  0x53,  0x8C,  0x38,  0xAF,  
  0x48,  0xCE,  0x01,  0x10,  0x00,  0x05,  0x00,  0x00,  
    
  0x5F,  0x18,  0x35,  0x17,  0x0B,  0x0F,  0xA0,  0x87,  0x93,  0x41,  0xB2,  0x66,  0x53,  0x8C,  0x38,  0xAF,  
  0x48,  0xCE,  0x01,  0x11,  0x00,  0x05,  0x00,  0x02,  
  
  // 第14个操作码,对应结构体EFI_IFR_GUID_LABEL
  0x5F,  // 操作码示EFI_IFR_GUID_OP
  0x15,  // 长度21个字节,其中16个是GUID
  // EFI_IFR_TIANO_GUID,表示GUIDed opcodes defined for EDKII implementation,定义在MdeModulePkg\\Include\\Guid\\MdeModuleHii.h
  0x35,  0x17,  0x0B,  0x0F,  0xA0,  0x87,  0x93,  0x41,  0xB2,  0x66,  0x53,  0x8C,  0x38,  0xAF,  0x48,  0xCE,  
  0x00,  // ExtendOpCode,0x00表示EFI_IFR_EXTEND_OP_LABEL
  0x00,  0x10,  // Label Number.
    
  0x5F,  0x15,  0x35,  0x17,  0x0B,  0x0F,  0xA0,  0x87,  0x93,  0x41,  0xB2,  0x66,  0x53,  0x8C,  0x38,  0xAF,  
  0x48,  0xCE,  0x00,  0xFF,  0xFF,  
    
  0x29,  0x02,  
    
  0x29,  0x02
;

这些数据就是IFR二进制,它们通过HiiAddPackages()安装:

  //
  // Publish our HII data
  //
  gFrontPagePrivate.HiiHandle = HiiAddPackages (
                                  &mFrontPageGuid,
                                  gFrontPagePrivate.DriverHandle,
                                  FrontPageVfrBin,
                                  UiAppStrings,
                                  NULL
                                  );

这样就可以通过gFrontPagePrivate.HiiHandle来访问安装的资源了。

跟uni文件一样,vfr文件也有两种使用方式,一种是定义中间文件的变量,另一种是直接定义二进制。以上的代码使用的是中间文件的方式,除去前面的4个字节,剩余的部分对应到一个结构体:

///
/// The header found at the start of each package.
///
typedef struct 
  UINT32  Length:24;
  UINT32  Type:8;
  // UINT8  Data[...];
 EFI_HII_PACKAGE_HEADER;
///
/// The Form package is used to carry form-based encoding data.
///
typedef struct _EFI_HII_FORM_PACKAGE_HDR 
  EFI_HII_PACKAGE_HEADER       Header;
  // EFI_IFR_OP_HEADER         OpCodeHeader;
  // More op-codes follow
 EFI_HII_FORM_PACKAGE_HDR;

EFI_HII_PACKAGE_HEADERType有如下的类型,它表示的是所有HII的类型,比如结构、字体、字符串等:

//
// Value of HII package type
//
#define EFI_HII_PACKAGE_TYPE_ALL             0x00
#define EFI_HII_PACKAGE_TYPE_GUID            0x01
#define EFI_HII_PACKAGE_FORMS                0x02
#define EFI_HII_PACKAGE_STRINGS              0x04
#define EFI_HII_PACKAGE_FONTS                0x05
#define EFI_HII_PACKAGE_IMAGES               0x06
#define EFI_HII_PACKAGE_SIMPLE_FONTS         0x07
#define EFI_HII_PACKAGE_DEVICE_PATH          0x08
#define EFI_HII_PACKAGE_KEYBOARD_LAYOUT      0x09
#define EFI_HII_PACKAGE_ANIMATIONS           0x0A
#define EFI_HII_PACKAGE_END                  0xDF
#define EFI_HII_PACKAGE_TYPE_SYSTEM_BEGIN    0xE0
#define EFI_HII_PACKAGE_TYPE_SYSTEM_END      0xFF

对于vfr文件描述的结构来说,其类型当然是EFI_HII_PACKAGE_FORMS,值是2。

头部之后的内容就是一个个的操作码,其结构体如下:

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

该结构体也是不定长的,之后跟有数据,根据不同的操作码,数据也不一样,如下图所示:

操作码已经在IFR操作码列出;长度包含整个不定长结构体,即也包含头部的长度;Scope如果是1就表示开启了一个新的Scope,直到遇到EFI_IFR_END_OP为止。

语言基础

vfr文件也使用EBNF来描述,这里就不再多介绍,只是简单说明基本语言基础。

  • 通过//来注释;
  • 支持预定义指令:
    • #define:跟c语言中的使用差不多,就是定义宏;
    • #include:可以包含c语言头文件;
    • #pragma:c语言头文件中会使用到,比如对于头文件中的结构体,可以设置其对齐方式。
  • 支持基本数据类型和HII特定的数据类型,也支持结构体:
    • UINT8UINT16UINT32UINT64BOOLEAN等;
    • EFI_STRING_IDEFI_HII_DATAEFI_HII_TIMEEFI_HII_REF等。

VFR组件

本文介绍常用的组件,比如按钮、选择框、标签等等,这些将结合IFR操作码说明,本节说明VFR的最基础组件。

formset

每个vfr文件中都有且只有一个formset,它构成了界面的主体,其它的界面组件都在它内部,其结构如下:

formset
  guid      = TCG_CONFIG_FORM_SET_GUID, // GUID
  title     = STRING_TOKEN(STR_TPM_TITLE), // uni文件中定义的标记
  help      = STRING_TOKEN(STR_TPM_HELP), // uni文件中定义的标记
  classguid = EFI_HII_PLATFORM_SETUP_FORMSET_GUID, // GUID,可选
  class     = EFI_NETWORK_DEVICE_CLASS, // 数值,可选
  subclass  = 0x03, // 数值,可选

  // 剩余组件写在这里

endformset;

剩余组件可以是图片、变量、DisableIf、SuppressIf、扩展等,用BNF表示就是:

vfrFormSetList ::=
(
    vfrFormDefinition
    | vfrFormMapDefinition
    | vfrStatementImage
    | vfrStatementVarStoreLinear
    | vfrStatementVarStoreEfi
    | vfrStatementVarStoreNameValue
    | vfrStatementDefaultStore
    | vfrStatementDisableIfFormSet
    | vfrStatementSuppressIfFormSet
    | vfrStatementExtension
)*

formsetendformset配对使用。

具体的层级关系如下(目前的源代码中能够找到的vfr组件):

注意这里只包含了当前EDK代码中存在的组件,还有很多没有列举。

form

formset内可以有若干个form,下面是一个例子:

  form formid = FORM_MAIN_ID,
    title = STRING_TOKEN(STR_FORM_MAIN_TITLE);
  endform;

formid在一个formset中必须是唯一的;title来自uni文件中的标记。

变量

VFR使用的变量有3种,分别是varstoreefivarstorenamevaluevarstore,下面是示例:

  //
  // Define a Buffer Storage (EFI_IFR_VARSTORE)
  //
  varstore DRIVER_SAMPLE_CONFIGURATION,     // 数据结构,在头文件中定义
    varid = CONFIGURATION_VARSTORE_ID,      // 变量ID,可选,在创建操作码的函数中会用到,比如HiiCreateOneOfOpCode()
    name  = MyIfrNVData,                    // 变量名,CHAR16     VariableName[] = L"MyIfrNVData";
    guid  = DRIVER_SAMPLE_FORMSET_GUID;     // 变量GUID,跟变量名共同确定了UEFI变量

  //
  // Define a EFI variable Storage (EFI_IFR_VARSTORE_EFI)
  //
  efivarstore MY_EFI_VARSTORE_DATA,  // 数据结构,在头文件中定义
    attribute = EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,  // UEFI变量属性
    name  = MyEfiVar,  // 变量名,CHAR16     MyEfiVar[] = L"MyEfiVar";
    guid  = DRIVER_SAMPLE_FORMSET_GUID;  // 变量GUID,跟变量名共同确定了UEFI变量

  //
  // 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 = DRIVER_SAMPLE_FORMSET_GUID;             // GUID of this Name/Value storage

上述的变量可以设置默认值,且默认值也可以不同,这通过defaultstore来实现:

  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 default

使用示例,可以在上述3中变量中使用:

    numeric varid   = MyIfrNVData.HowOldAreYouInYears,  // varstore
            prompt  = STRING_TOKEN(STR_NUMERIC_STEP_PROMPT),
            help    = STRING_TOKEN(STR_NUMERIC_HELP2),
            minimum = 0,
            maximum = 243,
            step    = 1,
            default = 18, defaultstore = MyStandardDefault,     // This is standard default value
            default = 19, defaultstore = MyManufactureDefault,  // This is manufacture default value

    endnumeric;    

	numeric varid   = MyEfiVar.Field8,                    // Reference of EFI variable storage
            questionid  = 0x1111,
            prompt  = STRING_TOKEN(STR_TALL_HEX_PROMPT),
            help    = STRING_TOKEN(STR_NUMERIC_HELP1),
            flags   = DISPLAY_UINT_HEX | INTERACTIVE,     // Display in HEX format (if not specified, default is in decimal format)
            minimum = 0,
            maximum = 250,
            default = 18, defaultstore = MyStandardDefault,     // This is standard default value
            default = 19, defaultstore = MyManufactureDefault,  // This is manufacture default value

    endnumeric;

    //
    // Define numeric using Name/Value Storage
    //
    numeric varid   = MyNameValueVar[0],     // This numeric take NameValueVar0 as storage
            prompt  = STRING_TOKEN(STR_NAME_VALUE_VAR_NAME0),
            help    = STRING_TOKEN(STR_NAME_VALUE_VAR_NAME0_HELP),
            //
            // Size should be defined for numeric when use Name/Value storage
            // Valid value for numerice size are: NUMERIC_SIZE_1, NUMERIC_SIZE_2, NUMERIC_SIZE_4 and NUMERIC_SIZE_8
            //
            flags   = NUMERIC_SIZE_1,        // Size of this numeric is 1 byte
            minimum = 0,
            maximum = 0xff,
            step    = 0,
            locked,
            default = 16, defaultstore = MyStandardDefault,     // This is standard default value
            default = 17, defaultstore = MyManufactureDefault,  // This is manufacture default value
    endnumeric;

控制

有如下的3种控制显示的语句:

    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;

    grayoutif NOT ideqval MyIfrNVData.SuppressGrayOutSomething == 0x1;
      suppressif questionref(MyOneOf) == 0x0;

        checkbox varid   = MyIfrNVData.ChooseToActivateNuclearWe

以上是关于UEFI实战HII之vfr文件的主要内容,如果未能解决你的问题,请参考以下文章

UEFI实战HII之代码示例

UEFI实战HII之代码示例

UEFI实战UEFI用户交互界面使用说明之VFR文件

UEFI实战HII之FrontPage

UEFI实战HII之FrontPage

UEFI实战HII之uni文件