U-Boot EFI SetVariable运行时服务支持及调试问题咨询
EFI变量设置问题分析与解决方案
背景场景
在概念验证项目中,构建了启用CONFIG_EFI_STUB的Linux内核镜像,通过U-Boot预启动的Shell.efi启动该镜像。挂载efivarfs后可查看Shell.efi设置的环境变量,操作及输出如下:
sh-5.1# mount -t efivarfs efivarfs /sys/firmware/efi/efivars sh-5.1# cd /sys/firmware/efi/efivars sh-5.1# mount | grep efivars efivarfs on /sys/firmware/efi/efivars type efivarfs (ro,relatime) sh-5.1# ls AuditMode-8be4df61-93ca-11d2-aa0d-00e098032b8c DeployedMode-8be4df61-93ca-11d2-aa0d-00e098032b8c OsIndications-8be4df61-93ca-11d2-aa0d-00e098032b8c OsIndicationsSupported-8be4df61-93ca-11d2-aa0d-00e098032b8c PlatformLang-8be4df61-93ca-11d2-aa0d-00e098032b8c PlatformLangCodes-8be4df61-93ca-11d2-aa0d-00e098032b8c SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c SetupMode-8be4df61-93ca-11d2-aa0d-00e098032b8c TEST2-8be4df61-93ca-11d2-aa0d-00e098032b8c VendorKeys-8be4df61-93ca-11d2-aa0d-00e098032b8c sh-5.1# hexdump TEST2-8be4df61-93ca-11d2-aa0d-00e098032b8c 0000000 0007 0000 0022 0048 0049 0022 000000c
其中TEST2-8be4df61-93ca-11d2-aa0d-00e098032b8c为Shell.efi设置的环境变量。
但将efivarfs重新挂载为读写模式后,从Linux设置EFI变量失败,操作及输出如下:
sh-5.1# mount -o remount,rw -t efivarfs efivarfs /sys/firmware/efi/efivars sh-5.1# mount | grep efivars efivarfs on /sys/firmware/efi/efivars type efivarfs (rw,relatime) sh-5.1# printf "\x07\x00\x00\x00\x00" > TESTVAR-8be4df61-93ca-11d2-aa0d-00e098032b8c sh: printf: write error: Invalid argument
经排查,U-Boot的EFI库中SetVariable的运行时实现是返回EFI_UNSUPPORTED的存根函数:
/** * efi_set_variable_runtime() - runtime implementation of SetVariable() * * @variable_name: name of the variable * @vendor: vendor GUID * @attributes: attributes of the variable * @data_size: size of the buffer with the variable value * @data: buffer with the variable value * Return: status code */ static efi_status_t __efi_runtime EFIAPI efi_set_variable_runtime(u16 *variable_name, const efi_guid_t *vendor, u32 attributes, efi_uintn_t data_size, const void *data) { return EFI_UNSUPPORTED; }
问题1:支持SetVariable运行时服务的可行方案
- 复用U-Boot非运行时EFI变量逻辑:U-Boot在启动阶段(非运行时)已有EFI变量读写能力,可将这部分逻辑适配到运行时环境,需注意运行时的内存地址转换限制,确保代码能在虚拟地址模式下正常执行。
- 对接硬件非易失性存储:直接适配平台上的SPI Flash、EEPROM等存储介质,实现独立的EFI变量存储管理,需处理存储分区、数据校验、磨损均衡等硬件相关逻辑。
- 开启U-Boot持久化变量配置:若平台支持,启用
CONFIG_EFI_VARS_PERSISTENT配置,让U-Boot将EFI变量持久化到指定介质,同时完善运行时SetVariable实现,复用已有持久化逻辑。 - 替换为完整EFI固件:若项目允许,将U-Boot替换为EDK2等原生支持完整EFI运行时服务的固件,这类固件自带成熟的
SetVariable实现。
问题2:从零实现SetVariable的注意事项与陷阱
- 运行时环境约束:运行时服务执行时系统已切换到虚拟地址空间,必须使用EFI提供的
efi_convert_pointer等函数处理地址转换,禁止直接访问物理内存。 - 存储原子性保障:写入EFI变量必须保证操作原子性,避免断电导致存储损坏,需实现事务性写入逻辑——先写入临时区域,验证成功后再替换原有数据。
- 严格遵循EFI属性规范:需正确处理
EFI_VARIABLE_NON_VOLATILE、EFI_VARIABLE_RUNTIME_ACCESS等属性,比如非易失性变量必须写入持久化存储,同时处理属性变更的兼容逻辑。 - 存储磨损与容量管理:Flash类存储有写入次数限制,需实现磨损均衡机制避免局部损坏;同时要跟踪存储容量,防止变量写入超出介质上限。
- 变量标识校验:EFI变量的唯一标识是「变量名+GUID」,实现时必须严格校验格式,避免出现变量冲突或无效条目。
- 兼容现有格式:要匹配Linux
efivarfs的变量格式要求,比如变量数据前的属性头信息,确保读写操作双向兼容。
问题3:为运行时服务启用调试日志的方法
直接用printk()导致段错误,是因为运行时环境已关闭控制台设备或内存映射发生变化,可行方案包括:
- 使用EFI调试接口:启用U-Boot的
CONFIG_EFI_DEBUG配置,使用EFI规范提供的调试输出宏/函数打印日志,利用EFI原生调试通道输出信息。 - 内存缓冲区日志:在运行时服务中预留一块固定内存缓冲区,将调试信息写入其中,后续在Linux内核中读取该缓冲区内容,需提前约定缓冲区的物理地址和大小。
- 硬件调试工具:通过JTAG、SWD等硬件调试接口断点调试
efi_set_variable_runtime函数,直接查看参数与执行流程,无需修改代码。 - 前置逻辑验证:先在非运行时阶段验证
SetVariable的核心逻辑,确保功能正确后再移植到运行时环境,减少运行时调试需求。
内容的提问来源于stack exchange,提问作者Rohit Khapare
相关产品推荐
相关产品推荐

