UEFI开发学习 31 – UEFI Hii基础及编程实例
多数人对主板固件的第一印象是蓝底白字的Setup界面——它作为UEFI固件与用户交互的唯一“可见层”,提供了CPU、内存、PCIe等设备的调整选项,支持用户定制化配置。
|
|
|
|
---|---|---|---|
|
|
|
|
|
|
|
|
这套架构类似浏览器运行网页并提交表单:
1.注册资源:固件驱动将字符串/字体/IFR资源打包为HII Package,注册到系统HII Database
2.渲染界面:Form Browser(UEFI内置浏览器)从Database检索资源,解析IFR字节码渲染为界面
3.交互提交:用户修改配置后,Browser通过EFI_HII_CONFIG_ACCESS_PROTOCOL将数据提交给后端C代码,后者处理硬件操作
Setup本质是固件内的“微型Web服务”——声明式UI配合一次性提交模式,兼顾多语言/多皮肤的灵活性,同时保留硬件控制精度。
一、HII 基础
1. HII 的概念与作用
HII(Human Interface Infrastructure)是 UEFI 规范中定义的人机交互基础设施,为固件提供标准化的用户界面管理框架。
核心设计理念:
资源与逻辑分离:将UI资源(字符串、表单、字体等)与驱动业务逻辑完全解耦
国际化支持:通过多语言字符串包实现本地化,同一驱动可适配不同语言环境
动态界面构建:使用IFR(Internal Forms Representation)二进制格式描述表单,支持运行时动态生成UI
标准化接口:提供统一的协议接口,驱动无需关心底层渲染实现
2. HII 的核心组成
|
|
|
---|---|---|
HII Database Protocol | EFI_HII_DATABASE_PROTOCOL |
|
HII String Protocol | EFI_HII_STRING_PROTOCOL |
|
HII Config Routing Protocol | EFI_HII_CONFIG_ROUTING_PROTOCOL |
|
HII Config Access Protocol | EFI_HII_CONFIG_ACCESS_PROTOCOL |
|
HII Font Protocol | EFI_HII_FONT_PROTOCOL |
|
Form Browser2 Protocol | EFI_FORM_BROWSER2_PROTOCOL |
|
3. HII 数据结构与资源类型
核心数据结构:
Package:HII中的最小资源单位,每个Package包含特定类型的数据
Package List:由EFI_HII_PACKAGE_LIST_HEADER开头,包含一个或多个Package的集合
HII Handle:Package List在HII Database中的唯一标识符
标准Package类型(UEFI规范定义):
|
|
|
|
---|---|---|---|
Strings Package | EFI_HII_PACKAGE_STRINGS | .uni
|
|
Forms Package | EFI_HII_PACKAGE_FORMS | .vfr
|
|
Fonts Package | EFI_HII_PACKAGE_FONTS |
|
|
Images Package | EFI_HII_PACKAGE_IMAGES |
|
|
Simple Fonts Package | EFI_HII_PACKAGE_SIMPLE_FONTS |
|
|
Device Path Package | EFI_HII_PACKAGE_DEVICE_PATH |
|
|
二、HII 编程
本节内容以一个HiiDemo源码为例,讨论Hii编程中,涉及的文件结构,语法,控件等内容。源码将附于文末链接中。
1. HII 编程的代码结构
|
|
|
---|---|---|
.c |
|
HiiDemo.c |
.vfr |
|
HiiDemoVfr.vfr |
.uni |
|
HiiDemoStrings.uni |
.inf |
|
HiiDemo.inf |
2. 文件编译与转换流程
在 EDK II 的构建过程中,这些源文件会被特殊的编译器和工具处理,最终转换为 C 语言的字节数组,链接到最终的 EFI 文件中。
HiiDemo 编译后,Build 目录下将生成相应的目录,如BuildMdeModuleDEBUG_VS2019X64MdeModulePkgApplicationHiiDemoHiiDemo,该目录下的中间文件涉及vfr和uni文件的转换。
1.UNI 文件的转换
#define STR_FORM_SET_TITLE 0x0002 #define STR_FORM_SET_TITLE_HELP 0x0003 // ... more string IDs
这些宏将在 VFR 文件中被引用,从而将界面控件与具体的字符串关联起来。
2.VFR 文件的转换
3.资源的使用
在 HiiDemo.c 中,这些自动生成的 C 数组(HiiDemoStrings 和 HiiDemoVfrBin)将作为资源被注册到 HII Database 中。这一步通常通过调用 HiiAddPackages 函数完成:
// mDriverHandle 是当前驱动的句柄 // HiiDemoStrings 和 HiiDemoVfrBin 是编译生成的数组 HiiHandle = HiiAddPackages ( &mHiiDemoFormSetGuid, // 唯一的 FormSet GUID mDriverHandle, HiiDemoStrings, HiiDemoVfrBin, NULL // 可以添加其他 Package,如 Image Package );
此函数执行后,HiiDemo 模块的界面资源就对整个系统可见了。Form Browser 可以随时根据需要来加载并显示它。
三、VFR 文件
1. VFR 文件的作用
定义 HII 表单界面
编译生成 IFR 二进制数据
包含 FormSet、VarStore、Form、控件、字符串 Token 引用
2. VFR 文件示例
/** @file VFR file for HII Demo Application. **/ #include#define HII_DEMO_FORMSET_GUID { 0x85b75607, 0xf7ce, 0x471e, { 0xb7, 0xe4, 0x2a, 0xea, 0x5f, 0x72, 0x32, 0xee } } #define HII_DEMO_FORM_ID 0x1000 typedefstruct { UINT8 CheckboxValue; UINT16 NumericValue; UINT16 OneOfValue; CHAR16 StringValue[20]; UINT8 OrderedListValue[3]; CHAR16 PasswordValue[16]; } HII_DEMO_CONFIGURATION; formset guid = HII_DEMO_FORMSET_GUID, title = STRING_TOKEN(STR_FORM_SET_TITLE), help = STRING_TOKEN(STR_FORM_SET_TITLE_HELP), classguid = EFI_HII_PLATFORM_SETUP_FORMSET_GUID, varstore HII_DEMO_CONFIGURATION, varid = 0x1234, name = HiiDemoConfig, guid = HII_DEMO_FORMSET_GUID; form formid = HII_DEMO_FORM_ID, title = STRING_TOKEN(STR_MAIN_FORM_TITLE); subtitle text = STRING_TOKEN(STR_SUBTITLE_TEXT); text help = STRING_TOKEN(STR_TEXT_HELP), text = STRING_TOKEN(STR_TEXT_PROMPT); checkbox varid = HiiDemoConfig.CheckboxValue, prompt = STRING_TOKEN(STR_CHECKBOX_PROMPT), help = STRING_TOKEN(STR_CHECKBOX_HELP), flags = CHECKBOX_DEFAULT, endcheckbox; numeric varid = HiiDemoConfig.NumericValue, prompt = STRING_TOKEN(STR_NUMERIC_PROMPT), help = STRING_TOKEN(STR_NUMERIC_HELP), minimum = 0, maximum = 100, step = 1, default = 50, endnumeric; oneof varid = HiiDemoConfig.OneOfValue, prompt = STRING_TOKEN(STR_ONEOF_PROMPT), help = STRING_TOKEN(STR_ONEOF_HELP), option text = STRING_TOKEN(STR_ONEOF_OPTION1), value = 1, flags = DEFAULT; option text = STRING_TOKEN(STR_ONEOF_OPTION2), value = 2, flags = 0; option text = STRING_TOKEN(STR_ONEOF_OPTION3), value = 3, flags = 0; endoneof; string varid = HiiDemoConfig.StringValue, prompt = STRING_TOKEN(STR_STRING_PROMPT), help = STRING_TOKEN(STR_STRING_HELP), minsize = 0, maxsize = 19, endstring; orderedlist varid = HiiDemoConfig.OrderedListValue, prompt = STRING_TOKEN(STR_ORDEREDLIST_PROMPT), help = STRING_TOKEN(STR_ORDEREDLIST_HELP), option text = STRING_TOKEN(STR_ORDEREDLIST_OPTION1), value = 1, flags = 0; option text = STRING_TOKEN(STR_ORDEREDLIST_OPTION2), value = 2, flags = 0; option text = STRING_TOKEN(STR_ORDEREDLIST_OPTION3), value = 3, flags = 0; endlist; subtitle text = STRING_TOKEN(STR_SUBTITLE_ADVANCED); grayoutif TRUE; text help = STRING_TOKEN(STR_GRAYOUT_HELP), text = STRING_TOKEN(STR_GRAYOUT_TEXT); endif; suppressif ideqval HiiDemoConfig.CheckboxValue == 0; text help = STRING_TOKEN(STR_SUPPRESS_HELP), text = STRING_TOKEN(STR_SUPPRESS_TEXT); endif; password varid = HiiDemoConfig.PasswordValue, prompt = STRING_TOKEN(STR_PASSWORD_PROMPT), help = STRING_TOKEN(STR_PASSWORD_HELP), minsize = 4, maxsize = 15, endpassword; text help = STRING_TOKEN(STR_ACTION_HELP), text = STRING_TOKEN(STR_ACTION_TEXT), flags = INTERACTIVE, key = 0x1001; endform; endformset;
3. 语法结构解析
文件头与 GUID 定义
#include#define HII_DEMO_FORMSET_GUID { ... } #define HII_DEMO_FORM_ID 0x1000
引入标准的 EFI_HII_PLATFORM_SETUP_FORMSET_GUID,用于标识这是一个 Setup 类的 FormSet
自定义了一个本示例的 FormSet GUID 和 Form ID
VarStore 定义
typedef struct { UINT8 CheckboxValue; UINT16 NumericValue; UINT16 OneOfValue; CHAR16 StringValue[20]; UINT8 OrderedListValue[3]; CHAR16 PasswordValue[16]; } HII_DEMO_CONFIGURATION;
定义了一个结构体 HII_DEMO_CONFIGURATION,作为表单数据存储(VarStore)
每个字段对应一个控件的变量绑定
FormSet 声明
formset guid = HII_DEMO_FORMSET_GUID, title = STRING_TOKEN(STR_FORM_SET_TITLE), help = STRING_TOKEN(STR_FORM_SET_TITLE_HELP), classguid = EFI_HII_PLATFORM_SETUP_FORMSET_GUID,
guid:本 FormSet 的唯一标识
title / help:标题与帮助文本(引用 .uni 文件中的字符串)
classguid:指定这是一个平台 Setup 类型的 FormSet
VarStore 声明
varstore HII_DEMO_CONFIGURATION, varid = 0x1234, name = HiiDemoConfig, guid = HII_DEMO_FORMSET_GUID;
绑定结构体类型 HII_DEMO_CONFIGURATION
varid:VarStore ID(在控件 varid 属性中引用)
name:变量名
guid:与 FormSet 关联的 GUID
Form 定义
form formid = HII_DEMO_FORM_ID, title = STRING_TOKEN(STR_MAIN_FORM_TITLE);
定义一个表单页面(Form)
formid:唯一 ID
title:页面标题
各类控件
条件显示控制
grayoutif TRUE; ... endif;
→ 永远灰显(不可用)
suppressif ideqval HiiDemoConfig.CheckboxValue == 0; ... endif;
→ 当复选框值为 0 时隐藏内容
Flags 详解与最佳实践
常用Flags及其UEFI规范定义:
|
|
|
|
|
---|---|---|---|---|
DEFAULT | EFI_IFR_OPTION_DEFAULT | oneof
|
|
option text=STRING_TOKEN(STR_OPTION1), value=1, flags=DEFAULT; |
CHECKBOX_DEFAULT | EFI_IFR_CHECKBOX_DEFAULT | checkbox |
|
checkbox varid=Config.Enable, flags=CHECKBOX_DEFAULT; |
INTERACTIVE | EFI_IFR_FLAG_CALLBACK |
|
|
|
RESET_REQUIRED | EFI_IFR_FLAG_RESET_REQUIRED |
|
|
|
MANUFACTURING | EFI_IFR_OPTION_DEFAULT_MFG | oneof
|
|
|
高级Flags组合使用:
// 示例1:交互式数值控件,修改后需要重启 numeric varid = Config.CpuRatio, prompt = STRING_TOKEN(STR_CPU_RATIO), help = STRING_TOKEN(STR_CPU_RATIO_HELP), flags = INTERACTIVE | RESET_REQUIRED, key = KEY_CPU_RATIO, minimum = 8, maximum = 50, step = 1, default = 16, endnumeric; // 示例2:制造模式与标准模式不同默认值 oneof varid = Config.TestMode, prompt = STRING_TOKEN(STR_TEST_MODE), help = STRING_TOKEN(STR_TEST_MODE_HELP), option text = STRING_TOKEN(STR_DISABLED), value = 0, flags = DEFAULT; option text = STRING_TOKEN(STR_ENABLED), value = 1, flags = MANUFACTURING; endoneof;
Flags使用注意事项:
INTERACTIVE控件必须指定key值,用于在Callback中识别事件源
RESET_REQUIRED会在Setup Browser中显示重启提示
多个Flags可以用|操作符组合使用
DEFAULT和MANUFACTURING可以同时存在于不同选项中
交互与 questionid / key
在 HII 中,每个能够与用户交互的控件都需要一个唯一的标识符,以便后端的 C 代码能够识别是哪个控件触发了事件。这个标识符就是 questionid。根据控件是否绑定到 varstore,questionid 的分配方式有所不同:
绑定 varstore 的控件 (varid)
隐式 Question ID (默认行为): 对于使用 varid 关键字绑定到 varstore 的控件(如 checkbox, numeric),系统会自动为其分配一个 questionid,其值等于该控件绑定的成员在 C 结构体中的偏移量 (offset)。这是最常见的基础用法。
显式 Question ID (高级实践): 尽管有隐式的 questionid,但在大型项目中,直接在 C 代码中使用偏移量(如 case 0x1A:)会降低代码的可读性和可维护性。因此,VFR 允许开发者为这些控件显式地指定一个 questionid(通常使用一个宏),从而覆盖默认的偏移量值。这样做的好处是:
增强可读性: C 代码可以使用有意义的宏名称(如 case MY_SETTING_ID:)代替晦涩的数字。
提升可维护性: 如果 varstore 的 C 结构体发生变化(如增删成员),所有后续成员的偏移量都会改变。如果 C 代码中硬编码了偏移量,就需要手动修改多处,极易出错。而使用显式的 questionid 则不受此影响。
未绑定 varstore 的控件 (key):
四、C 文件处理表单事件
1. 表单事件的来源与处理机制
HII Config Access Protocol的三大核心接口:
|
|
|
|
---|---|---|---|
ExtractConfig |
|
|
|
RouteConfig |
|
|
|
Callback |
|
|
|
事件触发的典型场景:
表单打开:Setup Browser调用ExtractConfig获取当前值
用户输入:INTERACTIVE控件触发Callback进行实时处理
表单提交:Setup Browser调用RouteConfig保存用户修改
表单关闭:可能再次调用ExtractConfig确认最终状态
2. EFI_HII_CONFIG_ACCESS_PROTOCOL 详解
协议结构定义:
typedef struct _EFI_HII_CONFIG_ACCESS_PROTOCOL { EFI_HII_ACCESS_EXTRACT_CONFIG ExtractConfig; EFI_HII_ACCESS_ROUTE_CONFIG RouteConfig; EFI_HII_ACCESS_FORM_CALLBACK Callback; } EFI_HII_CONFIG_ACCESS_PROTOCOL; // 协议GUID #define EFI_HII_CONFIG_ACCESS_PROTOCOL_GUID {0x330d4706, 0xf2a0, 0x4e4f, {0xa3, 0x69, 0xb6, 0x6f, 0xa8, 0xd5, 0x43, 0x85}}
ExtractConfig接口详解:
EFI_STATUS ExtractConfig( IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This, IN CONST EFI_STRING Request, //格式 OUT EFI_STRING *Progress, // 解析进度指针 OUT EFI_STRING *Results //格式输出 );
Request格式示例:GUID=
&NAME=
Results格式示例:GUID=
特殊情况:Request为NULL时,返回所有配置项
RouteConfig接口详解:
EFI_STATUS RouteConfig( IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This, IN CONST EFI_STRING Configuration, //格式 OUT EFI_STRING *Progress // 解析进度指针 );
Configuration:包含用户修改后的配置值
实现要点:需要验证数据有效性,更新内部VarStore
Callback接口详解:
EFI_STATUS Callback( IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This, IN EFI_BROWSER_ACTION Action, // 动作类型 IN EFI_QUESTION_ID QuestionId, // 控件ID IN UINT8 Type, // 数据类型 IN EFI_IFR_TYPE_VALUE *Value, // 当前值 OUT EFI_BROWSER_ACTION_REQUEST *ActionRequest // 返回动作 );
Action类型定义:
|
|
|
|
---|---|---|---|
EFI_BROWSER_ACTION_CHANGING |
|
|
|
EFI_BROWSER_ACTION_CHANGED |
|
|
|
EFI_BROWSER_ACTION_RETRIEVE |
|
|
|
EFI_BROWSER_ACTION_FORM_OPEN |
|
|
|
EFI_BROWSER_ACTION_FORM_CLOSE |
|
|
|
EFI_BROWSER_ACTION_SUBMITTED |
|
|
|
3. 完整的HII系统初始化流程
初始化
HiiDemo 驱动加载,在其入口函数中:
1.初始化一个 HII_DEMO_CONFIGURATION 结构体(mHiiDemoConfiguration)的默认值。
2.安装 EFI_DEVICE_PATH_PROTOCOL 和 EFI_HII_CONFIG_ACCESS_PROTOCOL 协议。这两个协议通常通过 InstallMultipleProtocolInterfaces 一同安装在驱动的 Handle 上。
Device Path 的作用: 为驱动创建一个唯一的“地址”。这非常重要,因为它让 HII Database 知道这个界面配置属于哪个设备或驱动。Form Browser 等系统组件通过这个路径来识别和管理特定的 HII 资源。没有它,HII Package 就像一个没有地址的信封,无法被准确投递和管理。
Config Access Protocol 的作用: 将 ExtractConfig, RouteConfig, Callback 函数的地址暴露出去,为界面提供后端数据处理能力。
3.调用 HiiAddPackages,将 VFR 和 UNI 编译好的二进制资源注册到 HII Database,并获得一个 HiiHandle。
显示表单
用户进入 UEFI Setup。Platform BDS(启动设备选择)代码会启动 Form Browser,并指示它显示所有 classguid 为 EFI_HII_PLATFORM_SETUP_FORMSET_GUID 的 formset。
数据加载
Form Browser 找到了 HiiDemo 注册的 formset:
Browser 调用 HiiDemo 的 ExtractConfig 函数。
ExtractConfig 内部使用 BlockToConfig 将 mHiiDemoConfiguration 结构体的内容转换成字符串,返回给 Browser。
界面渲染
Browser 拿到数据后,开始解析 Form Package (IFR 字节码):
它根据 IFR 的描述,逐一创建 checkbox, numeric 等控件,并将从 ExtractConfig 获得的值填充到这些控件中。
最终,一个完整的、带有当前配置值的界面呈现给用户。
用户交互
用户尝试修改 numeric 控件的值:
每次改动,Browser 都会调用 HiiDemo 的 Callback 函数,Action 为 EFI_BROWSER_ACTION_CHANGING 或 EFI_BROWSER_ACTION_CHANGED。
Callback 函数中的 C 代码可以根据 QuestionId 判断是哪个控件被修改,并执行相应的逻辑(如合法性检查、更新其他控件状态等)。
保存配置
用户完成所有修改,按下保存键(如 F10):
Browser 将所有被用户修改过的配置项打包成一个 config-string。
Browser 调用 HiiDemo 的 RouteConfig 函数,并将这个字符串作为参数传入。
RouteConfig 内部使用 ConfigToBlock 将字符串解析回 mHiiDemoConfiguration 结构体,完成了内存中数据的更新。
(可选)如果设计为持久化存储,RouteConfig 接下来会调用 gRT->SetVariable() 将更新后的 mHiiDemoConfiguration 结构体写入 NVRAM。
退出与清理
HiiDemo 驱动在被卸载时,会调用 HiiRemovePackages 从 HII Database 中移除它的资源,并卸载相关协议,完成清理工作。
五、总结
UEFI HII 提供了一套强大而灵活的框架,用于在固件层面构建现代化的用户界面。其核心优势在于:
解耦: 彻底分离了 UI 表现(VFR)和后端逻辑(C),使得两者可以独立开发和维护。
标准化: 提供了一套标准的控件和交互模型,确保了不同厂商固件之间的一致用户体验。
可扩展性: 轻松支持多语言本地化,并且可以通过自定义 Callback 逻辑实现极其复杂的动态交互。
HiiDemo 源码:https://gitee.com/ay123net/uefistudy
版权声明:
作者:bin
链接:https://ay123.net/mystudy/1909/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。

共有 0 条评论