UEFI HII表单回调:EDK II中表单提交事件处理

UEFI HII表单回调:EDK II中表单提交事件处理

【免费下载链接】edk2 EDK II 【免费下载链接】edk2 项目地址: https://gitcode.com/gh_mirrors/ed/edk2

1. HII表单回调概述

UEFI HII(Human Interface Infrastructure,人机交互基础设施)表单回调是EDK II(EFI Development Kit II)中实现用户交互的核心机制。当用户在UEFI设置界面中修改参数并提交时,固件通过回调函数(Callback Function)处理这些输入事件,实现配置数据的验证、存储和系统行为调整。本文将系统剖析HII表单回调的工作原理、实现流程及高级应用技巧,帮助开发者构建可靠的UEFI配置交互系统。

1.1 回调机制的核心价值

表单回调解决了UEFI环境下三大关键问题:

  • 输入验证:防止非法参数配置(如将内存频率设置超过硬件上限)
  • 依赖处理:实现动态配置逻辑(如选择"AHCI模式"后自动隐藏RAID相关选项)
  • 即时生效:支持关键配置的实时应用(如启用Secure Boot后立即验证签名)

mermaid

2. 回调函数原型与注册

2.1 EFI_HII_CONFIG_ACCESS_PROTOCOL

HII表单回调通过EFI_HII_CONFIG_ACCESS_PROTOCOL实现,该协议定义于MdePkg/Include/Protocol/HiiConfigAccess.h,核心结构如下:

typedef struct _EFI_HII_CONFIG_ACCESS_PROTOCOL {
  EFI_HII_CONFIG_ACCESS_EXTRACT_CONFIG  ExtractConfig;
  EFI_HII_CONFIG_ACCESS_ROUTE_CONFIG    RouteConfig;
  EFI_HII_CONFIG_ACCESS_CALLBACK        Callback;
} EFI_HII_CONFIG_ACCESS_PROTOCOL;

其中Callback字段即为表单提交事件处理函数,原型定义:

typedef
EFI_STATUS
(EFIAPI *EFI_HII_CONFIG_ACCESS_CALLBACK)(
  IN  CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
  IN  EFI_BROWSER_ACTION                   Action,
  IN  EFI_QUESTION_ID                      QuestionId,
  IN  UINT8                                Type,
  IN  EFI_IFR_TYPE_VALUE                   *Value,
  OUT EFI_BROWSER_ACTION_REQUEST          *ActionRequest
);

参数说明: | 参数名 | 类型 | 描述 | |--------|------|------| | This | IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL* | 指向当前协议实例的指针 | | Action | IN EFI_BROWSER_ACTION | 表单操作类型(如EFI_BROWSER_ACTION_CHANGED/EFI_BROWSER_ACTION_SUBMIT) | | QuestionId | IN EFI_QUESTION_ID | 触发事件的表单元素ID | | Type | IN UINT8 | 表单元素类型(如EFI_IFR_TYPE_NUMERIC/EFI_IFR_TYPE_STRING) | | Value | IN EFI_IFR_TYPE_VALUE* | 指向用户输入值的指针 | | ActionRequest | OUT EFI_BROWSER_ACTION_REQUEST* | 操作请求(如EFI_BROWSER_ACTION_REQUEST_NONE/EFI_BROWSER_ACTION_REQUEST_FORM_REDISPLAY) |

2.2 协议注册流程

在EDK II驱动中注册HII配置访问协议需完成以下步骤:

  1. 初始化协议结构体:实现ExtractConfigRouteConfigCallback函数
  2. 安装协议接口:通过gBS->InstallProtocolInterface()注册到系统
  3. 关联HII包:将协议实例与HII表单包(Form Package)绑定

示例代码框架:

EFI_HII_CONFIG_ACCESS_PROTOCOL mConfigAccess = {
  MyExtractConfig,
  MyRouteConfig,
  MyFormCallback
};

EFI_STATUS
EFIAPI
MyDriverEntry(
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
) {
  EFI_STATUS Status;
  
  // 安装HII配置访问协议
  Status = gBS->InstallProtocolInterface(
                  &mDriverHandle,
                  &gEfiHiiConfigAccessProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &mConfigAccess
                  );
  if (EFI_ERROR(Status)) {
    return Status;
  }
  
  // 创建并关联HII表单包
  mHiiHandle = HiiAddPackages(
                 &gMyDriverGuid,
                 ImageHandle,
                 MyFormBin,  // 编译后的IFR表单数据
                 MyStrings,  // 字符串包
                 NULL
                 );
  return EFI_SUCCESS;
}

3. 回调函数实现关键技术

3.1 Action类型处理

回调函数首要任务是识别操作类型,常见EFI_BROWSER_ACTION枚举值处理策略:

Action值含义处理策略
EFI_BROWSER_ACTION_CHANGED字段值变更实时验证输入合法性,更新依赖字段状态
EFI_BROWSER_ACTION_SUBMIT表单提交执行完整配置验证,保存数据并应用配置
EFI_BROWSER_ACTION_DEFAULT恢复默认值加载工厂默认配置,重置所有字段
EFI_BROWSER_ACTION_EXIT退出表单检查未保存更改,提示用户确认

典型Action处理代码:

EFI_STATUS
EFIAPI
MyFormCallback(
  IN  CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
  IN  EFI_BROWSER_ACTION                   Action,
  IN  EFI_QUESTION_ID                      QuestionId,
  IN  UINT8                                Type,
  IN  EFI_IFR_TYPE_VALUE                   *Value,
  OUT EFI_BROWSER_ACTION_REQUEST          *ActionRequest
) {
  *ActionRequest = EFI_BROWSER_ACTION_REQUEST_NONE;
  
  switch (Action) {
    case EFI_BROWSER_ACTION_CHANGED:
      return HandleValueChange(QuestionId, Type, Value);
    case EFI_BROWSER_ACTION_SUBMIT:
      return HandleFormSubmit(QuestionId, Type, Value, ActionRequest);
    case EFI_BROWSER_ACTION_DEFAULT:
      return RestoreDefaults();
    default:
      return EFI_UNSUPPORTED;
  }
}

3.2 输入验证与错误处理

健壮的输入验证是回调函数的核心职责,以下是内存超频配置的验证示例:

STATIC
EFI_STATUS
ValidateMemoryOverclock(
  IN UINT32 RequestedFreq
) {
  // 获取硬件支持的最大频率
  UINT32 MaxFreq = GetMemoryMaxFrequency();
  
  if (RequestedFreq > MaxFreq) {
    // 返回错误信息,HII框架会显示给用户
    return EFI_INVALID_PARAMETER;
  }
  
  // 检查是否超过散热设计上限
  if (RequestedFreq > (MaxFreq * 0.8)) {
    // 警告但允许继续(通过返回EFI_WARN_UNKNOWN_GLYPH)
    return EFI_WARN_UNKNOWN_GLYPH;
  }
  
  return EFI_SUCCESS;
}

错误处理最佳实践:

  1. 使用HiiSetErrorString()提供用户友好的错误提示
  2. 严重错误返回EFI_INVALID_PARAMETER阻止提交
  3. 警告性问题返回EFI_WARN系列状态码允许用户确认

3.3 配置数据存储

验证通过的配置数据需持久化存储,EDK II提供三种主要存储方式:

存储方式API适用场景
NV变量SetVariable()少量关键配置(如启动顺序、密码)
HII数据库HiiSetConfig()表单相关配置(如界面偏好、设备设置)
专用NVRAM区域自定义访问函数大量数据(如OEM配置参数块)

HII数据库存储示例:

STATIC
EFI_STATUS
SaveNetworkConfig(
  IN NETWORK_CONFIG *Config
) {
  EFI_STATUS Status;
  UINTN      ConfigSize = sizeof(NETWORK_CONFIG);
  UINT8      *ConfigBuffer;
  
  ConfigBuffer = AllocatePool(ConfigSize);
  if (ConfigBuffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
  
  CopyMem(ConfigBuffer, Config, ConfigSize);
  
  // 存储到HII数据库,路径格式:<ConfigGuid>://<Path>
  Status = HiiSetConfig(
             mHiiHandle,
             &gNetworkConfigGuid,
             L"Network/IPv4",
             ConfigBuffer,
             &ConfigSize
             );
  
  FreePool(ConfigBuffer);
  return Status;
}

4. 高级应用模式

4.1 动态表单重构

回调函数可请求HII框架重新显示表单,实现动态UI调整。典型应用是根据用户选择显示/隐藏相关选项:

// 当SATA模式从AHCI切换到RAID时,显示RAID配置选项
if (QuestionId == QID_SATA_MODE && NewValue == SATA_MODE_RAID) {
  *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_REDISPLAY;
  gRaidOptionsVisible = TRUE;
}

配合IFR表单中的grayoutif条件编译:

grayoutif ideqval QID_SATA_MODE SATA_MODE_AHCI;
  oneof varid = QID_RAID_LEVEL,
        prompt = "RAID Level",
        help = "Select RAID configuration level",
        option text = "RAID 0", value = 0x00, flags = 0;
        option text = "RAID 1", value = 0x01, flags = 0;
        option text = "RAID 10", value = 0x02, flags = 0;
  endoneof;
endif;

4.2 多表单协同

复杂配置场景需多个表单共享状态,可通过全局变量+回调同步实现:

mermaid

实现关键点:

  • 使用STATIC全局变量存储共享状态
  • ExtractConfig中同步表单显示状态
  • 通过EFI_EVENT_SIGNAL_EXIT_BOOT_SERVICES事件释放资源

4.3 异步操作处理

某些配置需要耗时操作(如刷新BIOS芯片),回调函数应避免阻塞UI,正确做法是:

  1. 返回EFI_NOT_READY并启动异步任务
  2. 通过EFI_HII_UPDATE_ACTION通知进度
  3. 完成后发送EFI_SIGNAL_READY_TO_BOOT事件
STATIC
EFI_STATUS
HandleFirmwareUpdate(
  IN EFI_HII_CONFIG_ACCESS_PROTOCOL *This
) {
  EFI_STATUS Status;
  
  // 启动异步更新任务
  Status = gBS->CreateEvent(
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  FirmwareUpdateTask,
                  NULL,
                  &mUpdateEvent
                  );
  if (EFI_ERROR(Status)) {
    return Status;
  }
  
  // 通知HII显示进度页面
  *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_REDISPLAY;
  return EFI_NOT_READY;  // 表示操作正在进行
}

5. 调试与优化技巧

5.1 回调函数调试工具

工具用法适用场景
UEFI Shelldmpstore查看NV变量验证配置是否正确保存
HII Database Explorerhiiutil -v检查表单注册和数据流
Serial DebugDEBUG((DEBUG_INFO, "Callback: QID=0x%x\n", QuestionId))跟踪回调执行流程

5.2 性能优化策略

  1. 减少回调次数:通过NVAR属性标记无需实时验证的字段
  2. 缓存硬件信息:避免每次回调重复读取SMBIOS/ACPI数据
  3. 批量处理:使用EFI_BROWSER_ACTION_SUBMIT一次性处理多字段变更
// 批量处理示例:仅在提交时验证所有字段
if (Action != EFI_BROWSER_ACTION_SUBMIT) {
  return EFI_SUCCESS;  // 跳过中间修改的验证
}

5.3 常见陷阱与规避

陷阱后果规避方法
回调中修改NV变量死锁风险使用EFI_TPL_CALLBACK级别事件延迟处理
返回未处理的Action配置丢失始终处理所有标准Action类型
忽略Type参数数据解析错误严格按Type字段解析Value联合体

6. 实战案例:Secure Boot配置回调

以下是实现Secure Boot开关控制的完整回调示例,包含状态验证、密钥管理和系统通知:

EFI_STATUS
EFIAPI
SecureBootCallback(
  IN  CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
  IN  EFI_BROWSER_ACTION                   Action,
  IN  EFI_QUESTION_ID                      QuestionId,
  IN  UINT8                                Type,
  IN  EFI_IFR_TYPE_VALUE                   *Value,
  OUT EFI_BROWSER_ACTION_REQUEST          *ActionRequest
) {
  EFI_STATUS Status;
  UINT8      SbEnabled;
  
  *ActionRequest = EFI_BROWSER_ACTION_REQUEST_NONE;
  
  if (QuestionId != QID_SECURE_BOOT_ENABLE) {
    return EFI_UNSUPPORTED;
  }
  
  if (Action == EFI_BROWSER_ACTION_SUBMIT) {
    // 解析输入值
    SbEnabled = Value->u8;
    
    if (SbEnabled) {
      // 启用Secure Boot前验证密钥
      Status = ValidateSecureBootKeys();
      if (EFI_ERROR(Status)) {
        HiiSetErrorString(mHiiHandle, NULL, L"Missing Platform Key (PK)", NULL);
        return EFI_INVALID_PARAMETER;
      }
      
      // 保存配置
      Status = SetVariable(
                 L"SecureBootEnable",
                 &gEfiGlobalVariableGuid,
                 EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
                 sizeof(UINT8),
                 &SbEnabled
                 );
      if (EFI_ERROR(Status)) {
        return Status;
      }
      
      // 通知安全驱动生效
      gBS->SignalEvent(mSecureBootEvent);
      
      *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_REDISPLAY;
    }
  }
  
  return EFI_SUCCESS;
}

7. 总结与展望

HII表单回调是EDK II中连接用户交互与系统配置的关键纽带,掌握其实现技巧对构建可靠UEFI固件至关重要。随着UEFI 2.10规范的普及,回调机制将支持更复杂的交互场景,如:

  • 触摸屏手势事件处理
  • 多语言语音输入解析
  • 远程配置管理接口

开发者应关注EFI_HII_CONFIG_ROUTING_PROTOCOL的扩展功能,以及EDK II中HiiConfigRoutingLib提供的高级路由能力,构建下一代UEFI人机交互系统。

实践建议

  1. 所有表单回调必须实现完整的错误处理
  2. 关键配置变更记录审计日志(通过DebugLib
  3. 遵循"验证-保存-通知"的三阶段处理模式
  4. 使用单元测试(如UnitTestFrameworkPkg)验证回调逻辑

【免费下载链接】edk2 EDK II 【免费下载链接】edk2 项目地址: https://gitcode.com/gh_mirrors/ed/edk2

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值